/*
 * Copyright (C) 2006 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.ohos;

import com.openharmony.recyclercomponent.ContainerGroup;
import com.openharmony.recyclercomponent.HarmonyConstant;
import com.openharmony.recyclercomponent.LogUtil;

import ohos.agp.components.*;
import ohos.agp.render.Canvas;
import ohos.agp.utils.Rect;
import ohos.app.Context;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

/**
 * A flexible view for providing a limited window into a large data set.
 */
public class RecyclerContainer extends ContainerGroup {
    /**
     * HORIZONTAL
     */
    public static final int HORIZONTAL = 0;
    /**
     * VERTICAL
     */
    public static final int VERTICAL = 1;
    /**
     * NO_POSITION
     */
    public static final int NO_POSITION = -1;
    /**
     * NO_ID
     */
    public static final long NO_ID = -1;
    /**
     * INVALID_TYPE
     */
    public static final int INVALID_TYPE = -1;

    private static final String TAG = "RecyclerContainer";
    private static final int MAX_SCROLL_DURATION = 2000;
    private final RecyclerViewDataObserver mObserver;
    private final Recycler mRecycler;
    ChildHelper mChildHelper;
    private final Runnable mUpdateChildViewsRunnable = new Runnable() {
        public void run() {
            mEatRequestLayout = true;
            updateChildViews();
            mEatRequestLayout = false;
        }
    };
    private final Rect mTempRect = new Rect();
    private final ArrayList<UpdateOp> mPendingUpdates = new ArrayList<UpdateOp>();
    private Pools.Pool<UpdateOp> mUpdateOpPool = new Pools.SimplePool<UpdateOp>(UpdateOp.POOL_SIZE);
    private Adapter mAdapter;
    private LayoutManager mLayout;
    private RecyclerListener mRecyclerListener;
    private final ArrayList<ItemDecoration> mItemDecorations = new ArrayList<ItemDecoration>();
    private final ArrayList<ItemTouchListener> mItemTouchListeners =
            new ArrayList<ItemTouchListener>();
    private final SparseArrayCompat<ViewHolder> mAttachedViewsByPosition =
            new SparseArrayCompat<ViewHolder>();
    private final LongSparseArray<ViewHolder> mAttachedViewsById =
            new LongSparseArray<ViewHolder>();
    private boolean mIsAttached;
    private boolean mHasFixedSize;
    private boolean mFirstLayoutComplete;
    private boolean mEatRequestLayout;
    private boolean mAdapterUpdateDuringMeasure;
    private final boolean mPostUpdatesOnAnimation;
    private RecycledViewPool mRecyclerPool;
    private EdgeEffectCompat mLeftGlow, mTopGlow, mRightGlow, mBottomGlow;
    RecyclerContainer.ItemAnimator mItemAnimator;
    boolean mLayoutFrozen;
    boolean mLayoutWasDefered;
    boolean mItemsAddedOrRemoved;
    LayoutPrefetchRegistryImpl mPrefetchRegistry;
    private List<RecyclerContainer.OnChildAttachStateChangeListener> mOnChildAttachStateListeners;
    boolean mPostedAnimatorRunner;
    boolean mDataSetHasChangedAfterLayout;
    private Runnable mItemAnimatorRunner;
    private int mInterceptRequestLayoutDepth;
    private final ProcessCallback mViewInfoProcessCallback;
    private static final int INVALID_POINTER = -1;
    /**
     * The RecyclerContainer is not currently scrolling.
     * see #getScrollState()
     */
    public static final int SCROLL_STATE_IDLE = 0;
    /**
     * The RecyclerContainer is currently being dragged by outside input such as user touch input.
     * see #getScrollState()
     */
    public static final int SCROLL_STATE_DRAGGING = 1;
    /**
     * The RecyclerContainer is currently animating to a final position while not under
     * outside control.
     * see #getScrollState()
     */
    public static final int SCROLL_STATE_SETTLING = 2;
    // Touch/scrolling handling
    private int mScrollState = SCROLL_STATE_IDLE;
    private int mScrollPointerId = INVALID_POINTER;
    private VelocityTracker mVelocityTracker;
    private int mInitialTouchX;
    private int mInitialTouchY;
    private int mLastTouchX;
    private int mLastTouchY;
    private final int mTouchSlop;
    private final int mMinFlingVelocity;
    private final int mMaxFlingVelocity;
    private final ViewFlinger mViewFlinger;
    private OnScrollListener mScrollListener;
    private static final Interpolator sQuinticInterpolator = new Interpolator() {
        public float getInterpolation(float t) {
            t -= 1.0f;
            return t * t * t * t * t + 1.0f;
        }
    };

    public RecyclerContainer(Context context) {
        this(context, null);
    }

    public RecyclerContainer(Context context, AttrSet attrs) {
        this(context, attrs, 0);
    }

    public RecyclerContainer(Context context, AttrSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        final int version = 16;
        mPostUpdatesOnAnimation = version >= 16;
        this.mObserver = new RecyclerViewDataObserver();
        this.mRecycler = new RecyclerContainer.Recycler();
        this.mViewFlinger = new ViewFlinger();
        final ViewConfiguration vc = ViewConfiguration.get(context);
        mTouchSlop = vc.getScaledTouchSlop();
        this.mInterceptRequestLayoutDepth = 0;
        this.mPostedAnimatorRunner = false;
        this.mItemsAddedOrRemoved = false;
        this.mDataSetHasChangedAfterLayout = false;
        this.mPrefetchRegistry = new LayoutPrefetchRegistryImpl();
        this.initChildrenHelper();
        this.mItemAnimatorRunner = new Runnable() {
            public void run() {
                if (RecyclerContainer.this.mItemAnimator != null) {
                    RecyclerContainer.this.mItemAnimator.runPendingAnimations();
                }
                RecyclerContainer.this.mPostedAnimatorRunner = false;
            }
        };

        this.mViewInfoProcessCallback = new ProcessCallback() {
            public void processDisappeared(RecyclerContainer.ViewHolder viewHolder,
                                           RecyclerContainer.ItemAnimator.ItemHolderInfo info, RecyclerContainer.ItemAnimator.ItemHolderInfo postInfo) {
                RecyclerContainer.this.mRecycler.unscrapView(viewHolder);
                RecyclerContainer.this.animateDisappearance(viewHolder, info, postInfo);
            }

            public void processAppeared(RecyclerContainer.ViewHolder viewHolder,
                                        RecyclerContainer.ItemAnimator.ItemHolderInfo preInfo, RecyclerContainer.ItemAnimator.ItemHolderInfo info) {
                RecyclerContainer.this.animateAppearance(viewHolder, preInfo, info);
            }

            public void processPersistent(RecyclerContainer.ViewHolder viewHolder,
                                          RecyclerContainer.ItemAnimator.ItemHolderInfo preInfo, RecyclerContainer.ItemAnimator.ItemHolderInfo postInfo) {
                viewHolder.setIsRecyclable(false);
                if (RecyclerContainer.this.mDataSetHasChangedAfterLayout) {
                    if (RecyclerContainer.this.mItemAnimator.animateChange(viewHolder, viewHolder, preInfo, postInfo)) {
                        RecyclerContainer.this.postAnimationRunner();
                    }
                } else if (RecyclerContainer.this.mItemAnimator.animatePersistence(viewHolder, preInfo, postInfo)) {
                    RecyclerContainer.this.postAnimationRunner();
                }

            }

            public void unused(RecyclerContainer.ViewHolder viewHolder) {
                RecyclerContainer.this.mLayout.removeAndRecycleView(viewHolder.itemView, RecyclerContainer.this.mRecycler);
            }
        };

        mMinFlingVelocity = vc.getScaledMinimumFlingVelocity();
        mMaxFlingVelocity = vc.getScaledMaximumFlingVelocity();
    }

    private void initChildrenHelper() {
        this.mChildHelper = new ChildHelper(new ChildHelper.Callback() {
            public int getChildCount() {
                return RecyclerContainer.this.getChildCount();
            }

            public void addView(Component child, int index) {
                RecyclerContainer.this.addView(child, index);
                RecyclerContainer.this.dispatchChildAttached(child);
            }

            public int indexOfChild(Component view) {
                return RecyclerContainer.this.getChildIndex(view);
            }

            public void removeViewAt(int index) {
                Component child = RecyclerContainer.this.getChildAt(index);
                if (child != null) {
                    RecyclerContainer.this.dispatchChildDetached(child);
                }
                RecyclerContainer.this.removeViewAt(index);
            }

            public Component getChildAt(int offset) {
                return RecyclerContainer.this.getChildAt(offset);
            }

            public void removeAllViews() {
                int count = this.getChildCount();

                for(int i = 0; i < count; ++i) {
                    Component child = this.getChildAt(i);
                    RecyclerContainer.this.dispatchChildDetached(child);
                }

                RecyclerContainer.this.removeAllViews();
            }

            public RecyclerContainer.ViewHolder getChildViewHolder(Component view) {
                return RecyclerContainer.getChildViewHolderInt(view);
            }

            public void attachViewToParent(Component child, int index, RecyclerContainer.LayoutConfig layoutParams) {
                RecyclerContainer.ViewHolder vh = RecyclerContainer.getChildViewHolderInt(child);
                if (vh != null) {
                    if (!vh.isTmpDetached() && !vh.shouldIgnore()) {
                        throw new IllegalArgumentException("Called attach on a child which is not detached: " + vh + RecyclerContainer.this.exceptionLabel());
                    }
                    vh.clearTmpDetachFlag();
                }
                RecyclerContainer.this.addView(child, index, layoutParams);
            }

            public void detachViewFromParent(int offset) {
                Component view = this.getChildAt(offset);
                if (view != null) {
                    RecyclerContainer.ViewHolder vh = RecyclerContainer.getChildViewHolderInt(view);
                    if (vh != null) {
                        if (vh.isTmpDetached() && !vh.shouldIgnore()) {
                            throw new IllegalArgumentException("called detach on an already detached child " + vh + RecyclerContainer.this.exceptionLabel());
                        }
                        vh.addFlags(256);
                    }
                }
                RecyclerContainer.this.removeViewAt(offset);
            }

            public void onEnteredHiddenState(Component child) {
                RecyclerContainer.ViewHolder vh = RecyclerContainer.getChildViewHolderInt(child);
            }

            public void onLeftHiddenState(Component child) {
                RecyclerContainer.ViewHolder vh = RecyclerContainer.getChildViewHolderInt(child);
            }
        });
    }

    void animateAppearance(RecyclerContainer.ViewHolder itemHolder, RecyclerContainer.ItemAnimator.ItemHolderInfo preLayoutInfo,
                           RecyclerContainer.ItemAnimator.ItemHolderInfo postLayoutInfo) {
        itemHolder.setIsRecyclable(false);
        if (this.mItemAnimator.animateAppearance(itemHolder, preLayoutInfo, postLayoutInfo)) {
            this.postAnimationRunner();
        }
    }

    void animateDisappearance(RecyclerContainer.ViewHolder holder, RecyclerContainer.ItemAnimator.ItemHolderInfo preLayoutInfo,
                              RecyclerContainer.ItemAnimator.ItemHolderInfo postLayoutInfo) {
        this.addAnimatingView(holder);
        holder.setIsRecyclable(false);
        if (this.mItemAnimator.animateDisappearance(holder, preLayoutInfo, postLayoutInfo)) {
            this.postAnimationRunner();
        }
    }

    void offsetPositionRecordsForRemove(int positionStart, int itemCount, boolean applyToPreLayout) {
        int positionEnd = positionStart + itemCount;
        int childCount = this.mChildHelper.getUnfilteredChildCount();

        for(int i = 0; i < childCount; ++i) {
            RecyclerContainer.ViewHolder holder = getChildViewHolderInt(this.mChildHelper.getUnfilteredChildAt(i));
            if (holder != null && !holder.shouldIgnore()) {
                if (holder.mPosition >= positionEnd) {
                    holder.offsetPosition(-itemCount, applyToPreLayout);
                } else if (holder.mPosition >= positionStart) {
                    holder.flagRemovedAndOffsetPosition(positionStart - 1, -itemCount, applyToPreLayout);
                }
            }
        }
        this.mRecycler.offsetPositionRecordsForRemove(positionStart, itemCount, applyToPreLayout);
        this.requestLayout();
    }

    /**
     * RecyclerContainer can perform several optimizations if it can know in advance that changes in
     * adapter content cannot change the size of the RecyclerContainer itself.
     * If your use of RecyclerContainer falls into this category, set this to true.
     *
     * @param hasFixedSize true if adapter changes cannot affect the size of the RecyclerContainer.
     */
    public void setHasFixedSize(boolean hasFixedSize) {
        mHasFixedSize = hasFixedSize;
    }

    /**
     * hasFixedSize
     *
     * @return true if the app has specified that changes in adapter content cannot change
     * the size of the RecyclerContainer itself.
     */
    public boolean hasFixedSize() {
        return mHasFixedSize;
    }

    /**
     * Set a new adapter to provide child views on demand.
     *
     * @param adapter The new adapter to set, or null to set no adapter.
     */
    public void setAdapter(Adapter adapter) {
        if (mAdapter != null) {
            this.mAdapter.unregisterAdapterDataObserver(this.mObserver);
            this.mAdapter.onDetachedFromRecyclerView(this);
        }

        this.removeAndRecycleViews();
        RecyclerContainer.Adapter oldAdapter = this.mAdapter;
        this.mAdapter = adapter;

        if (adapter != null) {
            adapter.registerAdapterDataObserver(mObserver);
            adapter.onAttachedToRecyclerView(this);
        }

        if (this.mLayout != null) {
            this.mLayout.onAdapterChanged(oldAdapter, this.mAdapter);
        }

        this.mRecycler.onAdapterChanged(oldAdapter, this.mAdapter, false);
        this.requestLayout();
    }

    /**
     * Retrieves the previously set adapter or null if no adapter is set.
     *
     * @return The previously set adapter
     */
    public Adapter getAdapter() {
        return mAdapter;
    }

    /**
     * Register a listener that will be notified whenever a child view is recycled.
     *
     * <p>This listener will be called when a LayoutManager or the RecyclerContainer decides
     * that a child view is no longer needed. If an application associates expensive
     * or heavyweight data with item views, this may be a good place to release
     * or free those resources.</p>
     *
     * @param listener Listener to register, or null to clear
     */
    public void setRecyclerListener(RecyclerListener listener) {
        mRecyclerListener = listener;
    }

    /**
     * Set the that this RecyclerContainer will use.
     *
     * <p>In contrast to other adapter-backed views such as RecyclerContainer allows client code to provide custom
     * layout arrangements for child views. These arrangements are controlled by the
     * RecyclerContainer.
     * A LayoutManager must be provided for RecyclerContainer to function.</p>
     *
     * <p>Several default strategies are provided for common uses such as lists and grids.</p>
     *
     * @param layout LayoutManager to use
     */
    public void setLayoutManager(LayoutManager layout) {
        if (layout != this.mLayout) {
            if (mLayout != null) {
                if (this.mItemAnimator != null) {
                    this.mItemAnimator.endAnimations();
                }

                this.mLayout.removeAndRecycleAllViews(this.mRecycler);
                this.mLayout.removeAndRecycleScrapInt(this.mRecycler);
                this.mRecycler.clear();

                if (this.mIsAttached) {
                    this.mLayout.dispatchDetachedFromWindow(this, this.mRecycler);
                }
                this.mLayout.setRecyclerView((RecyclerContainer) null);
                this.mLayout = null;
            } else {
                this.mRecycler.clear();
            }

            this.mChildHelper.removeAllViewsUnfiltered();
            this.mLayout = layout;
            if (layout != null) {
                if (layout.mRecyclerView != null) {
                    throw new IllegalArgumentException("LayoutManager " + layout +
                            " is already attached to a RecyclerContainer: " + layout.mRecyclerView);
                }
                this.mLayout.setRecyclerView(this);
                if (this.mIsAttached) {
                    this.mLayout.dispatchAttachedToWindow(this);
                }
            }
            this.mRecycler.updateViewCacheSize();
            this.requestLayout();
        }
    }

    /**
     * Retrieve this RecyclerContainer's {link RecycledViewPool}. This method will never return null;
     * if no pool is set for this view a new one will be created
     *
     * @return The pool used to store recycled item views for reuse.
     */
    public RecycledViewPool getRecycledViewPool() {
        if (mRecyclerPool == null) {
            mRecyclerPool = new RecycledViewPool();
        }
        return mRecyclerPool;
    }

    /**
     * Recycled view pools allow multiple RecyclerViews to share a common pool of scrap views.
     * This can be useful if you have multiple RecyclerViews with adapters that use the same
     * view types, for example if you have several data sets with the same kinds of item views
     * displayed.
     *
     * @param pool Pool to set. If this parameter is null a new pool will be created and used.
     */
    public void setRecycledViewPool(RecycledViewPool pool) {
        mRecyclerPool = pool;
    }

    /**
     * Return the current scrolling state of the RecyclerContainer.
     *
     * @return {link #SCROLL_STATE_IDLE}, {link #SCROLL_STATE_DRAGGING} or
     * {link #SCROLL_STATE_SETTLING}
     */
    public int getScrollState() {
        return mScrollState;
    }

    private void setScrollState(int state) {
        if (state == mScrollState) {
            return;
        }
        mScrollState = state;
        if (state != SCROLL_STATE_SETTLING) {
            mViewFlinger.stop();
        }
        if (mScrollListener != null) {
            mScrollListener.onScrollStateChanged(state);
        }
    }

    /**
     * Add an {link ItemDecoration} to this RecyclerContainer. Item decorations can
     * affect both measurement and drawing of individual item views.
     *
     * <p>Item decorations are ordered. Decorations placed earlier in the list will
     * be run/queried/drawn first for their effects on item views. Padding added to views
     * will be nested; a padding added by an earlier decoration will mean further
     * item decorations in the list will be asked to draw/pad within the previous decoration's
     * given area.</p>
     *
     * @param decor Decoration to add
     * @param index Position in the decoration chain to insert this decoration at. If this value
     *              is negative the decoration will be added at the end.
     */
    public void addItemDecoration(ItemDecoration decor, int index) {
        if (mItemDecorations.isEmpty()) {
            setWillNotDraw(false);
        }
        if (index < 0) {
            mItemDecorations.add(decor);
        } else {
            mItemDecorations.add(index, decor);
        }
    }

    /**
     * Add an {link ItemDecoration} to this RecyclerContainer. Item decorations can
     * affect both measurement and drawing of individual item views.
     *
     * <p>Item decorations are ordered. Decorations placed earlier in the list will
     * be run/queried/drawn first for their effects on item views. Padding added to views
     * will be nested; a padding added by an earlier decoration will mean further
     * item decorations in the list will be asked to draw/pad within the previous decoration's
     * given area.</p>
     *
     * @param decor Decoration to add
     */
    public void addItemDecoration(ItemDecoration decor) {
        addItemDecoration(decor, -1);
    }

    /**
     * Remove an {link ItemDecoration} from this RecyclerContainer.
     *
     * <p>The given decoration will no longer impact </p>
     *
     * @param decor Decoration to remove
     */
    public void removeItemDecoration(ItemDecoration decor) {
        mItemDecorations.remove(decor);
    }

    /**
     * Set a listener that will be notified of any changes in scroll state or position.
     *
     * @param listener Listener to set or null to clear
     */
    public void setOnScrollListener(OnScrollListener listener) {
        mScrollListener = listener;
    }

    public void scrollTo(int x, int y) {
    }

    @Override
    public void scrollBy(int x, int y) {
        if (mLayout == null) {
            throw new IllegalStateException("Cannot scroll without a LayoutManager set. " +
                    "Call setLayoutManager with a non-null argument.");
        }
        final boolean canScrollHorizontal = mLayout.canScrollHorizontally();
        final boolean canScrollVertical = mLayout.canScrollVertically();
        if (canScrollHorizontal || canScrollVertical) {
            scrollByInternal(canScrollHorizontal ? x : 0, canScrollVertical ? y : 0);
        } else {
            LogUtil.debug(TAG, "scrollBy canot scroll canScrollHorizontal || canScrollVertical false");
        }
    }

    /**
     * Does not perform bounds checking. Used by internal methods that have already validated input.
     *
     * @param x int
     * @param y int
     */
    void scrollByInternal(int x, int y) {
        int overscrollX = 0, overscrollY = 0;
        if (x != 0) {
            mEatRequestLayout = true;
            final int hresult = mLayout.scrollHorizontallyBy(x, getAdapter(), mRecycler);
            mEatRequestLayout = false;
            overscrollX = x - hresult;
        }
        if (y != 0) {
            mEatRequestLayout = true;
            final int vresult = mLayout.scrollVerticallyBy(y, getAdapter(), mRecycler);
            mEatRequestLayout = false;
            overscrollY = y - vresult;
        }
    }

    /**
     * Animate a scroll by the given amount of pixels along either axis.
     *
     * @param dx Pixels to scroll horizontally
     * @param dy Pixels to scroll vertically
     */
    public void smoothScrollBy(int dx, int dy) {
        if (dx != 0 || dy != 0) {
            mViewFlinger.smoothScrollBy(dx, dy);
        }
    }

    /**
     * Begin a standard fling with an initial velocity along each axis in pixels per second.
     *
     * @param velocityX Initial horizontal velocity in pixels per second
     * @param velocityY Initial vertical velocity in pixels per second
     */
    public void fling(int velocityX, int velocityY) {
        if (Math.abs(velocityX) < mMinFlingVelocity) {
            velocityX = 0;
        }
        if (Math.abs(velocityY) < mMinFlingVelocity) {
            velocityY = 0;
        }
        velocityX = Math.max(-mMaxFlingVelocity, Math.min(velocityX, mMaxFlingVelocity));
        velocityY = Math.max(-mMaxFlingVelocity, Math.min(velocityY, mMaxFlingVelocity));
        if (velocityX != 0 || velocityY != 0) {
            mViewFlinger.fling(velocityX, velocityY);
        }
    }

    /**
     * Apply a pull to relevant overscroll glow effects
     *
     * @param overscrollX int
     * @param overscrollY int
     */
    private void pullGlows(int overscrollX, int overscrollY) {
        if (overscrollX < 0) {
            if (mLeftGlow == null) {
                mLeftGlow = new EdgeEffectCompat(getContext());
                mLeftGlow.setSize(getMeasuredHeight() - getPaddingTop() - getPaddingBottom(),
                        getMeasuredWidth() - getPaddingLeft() - getPaddingRight());
            }
            mLeftGlow.onPull(-overscrollX / (float) getWidth());
        } else if (overscrollX > 0) {
            if (mRightGlow == null) {
                mRightGlow = new EdgeEffectCompat(getContext());
                mRightGlow.setSize(getMeasuredHeight() - getPaddingTop() - getPaddingBottom(),
                        getMeasuredWidth() - getPaddingLeft() - getPaddingRight());
            }
            mRightGlow.onPull(overscrollX / (float) getWidth());
        }
        if (overscrollY < 0) {
            if (mTopGlow == null) {
                mTopGlow = new EdgeEffectCompat(getContext());
                mTopGlow.setSize(getMeasuredWidth() - getPaddingLeft() - getPaddingRight(),
                        getMeasuredHeight() - getPaddingTop() - getPaddingBottom());
            }
            mTopGlow.onPull(-overscrollY / (float) getHeight());
        } else if (overscrollY > 0) {
            if (mBottomGlow == null) {
                mBottomGlow = new EdgeEffectCompat(getContext());
                mBottomGlow.setSize(getMeasuredWidth() - getPaddingLeft() - getPaddingRight(),
                        getMeasuredHeight() - getPaddingTop() - getPaddingBottom());
            }
            mBottomGlow.onPull(overscrollY / (float) getHeight());
        }
        if (overscrollX != 0 || overscrollY != 0) {
            postInvalidateOnAnimation(this);
        }
    }

    private void releaseGlows() {
        boolean needsInvalidate = false;
        if (mLeftGlow != null) needsInvalidate |= mLeftGlow.onRelease();
        if (mTopGlow != null) needsInvalidate |= mTopGlow.onRelease();
        if (mRightGlow != null) needsInvalidate |= mRightGlow.onRelease();
        if (mBottomGlow != null) needsInvalidate |= mBottomGlow.onRelease();
        if (needsInvalidate) {
            postInvalidateOnAnimation(this);
        }
    }

    void absorbGlows(int velocityX, int velocityY) {
        if (velocityX < 0) {
            if (mLeftGlow == null) {
                mLeftGlow = new EdgeEffectCompat(getContext());
                mLeftGlow.setSize(getMeasuredHeight() - getPaddingTop() - getPaddingBottom(),
                        getMeasuredWidth() - getPaddingLeft() - getPaddingRight());
            }
            mLeftGlow.onAbsorb(-velocityX);
        } else if (velocityX > 0) {
            if (mRightGlow == null) {
                mRightGlow = new EdgeEffectCompat(getContext());
                mRightGlow.setSize(getMeasuredHeight() - getPaddingTop() - getPaddingBottom(),
                        getMeasuredWidth() - getPaddingLeft() - getPaddingRight());
            }
            mRightGlow.onAbsorb(velocityX);
        }
        if (velocityY < 0) {
            if (mTopGlow == null) {
                mTopGlow = new EdgeEffectCompat(getContext());
                mTopGlow.setSize(getMeasuredWidth() - getPaddingLeft() - getPaddingRight(),
                        getMeasuredHeight() - getPaddingTop() - getPaddingBottom());
            }
            mTopGlow.onAbsorb(-velocityY);
        } else if (velocityY > 0) {
            if (mBottomGlow == null) {
                mBottomGlow = new EdgeEffectCompat(getContext());
                mBottomGlow.setSize(getMeasuredWidth() - getPaddingLeft() - getPaddingRight(),
                        getMeasuredHeight() - getPaddingTop() - getPaddingBottom());
            }
            mBottomGlow.onAbsorb(velocityY);
        }
        if (velocityX != 0 || velocityY != 0) {
            postInvalidateOnAnimation(this);
        }
    }

    // Focus handling
    @Override
    public Component focusSearch(Component focused, int direction) {
        Component result = null;
        if (result == null) {
            mEatRequestLayout = true;
            result = mLayout.onFocusSearchFailed(focused, direction, mRecycler);
            mEatRequestLayout = false;
        }
        return result != null ? result : super.focusSearch(focused, direction);
    }

    @Override
    public void requestChildFocus(Component child, Component focused) {
        if (!mLayout.onRequestChildFocus(child, focused)) {
            mTempRect.set(0, 0, focused.getWidth(), focused.getHeight());
            offsetDescendantRectToMyCoords(focused, mTempRect);
            offsetRectIntoDescendantCoords(child, mTempRect);
            requestChildRectangleOnScreen(child, mTempRect, !mFirstLayoutComplete);
        }
        super.requestChildFocus(child, focused);
    }

    @Override
    public boolean requestChildRectangleOnScreen(Component child, Rect rect, boolean immediate) {
        return mLayout.requestChildRectangleOnScreen(child, rect, immediate);
    }

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        mIsAttached = true;
        mFirstLayoutComplete = false;
        if (mLayout != null) {
            mLayout.onAttachedToWindow();
        }
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        if (this.mItemAnimator != null) {
            this.mItemAnimator.endAnimations();
        }
        mFirstLayoutComplete = false;
        mViewFlinger.stop();
        mIsAttached = false;
        if (mLayout != null) {
            mLayout.onDetachedFromWindow();
        }
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent e) {
        final boolean canScrollHorizontally = mLayout.canScrollHorizontally();
        final boolean canScrollVertically = mLayout.canScrollVertically();
        if (mVelocityTracker == null) {
            mVelocityTracker = VelocityTracker.obtain();
        }
        mVelocityTracker.addMovement(e);
        final int action = MotionEventCompat.getActionMasked(e);
        final int actionIndex = MotionEventCompat.getActionIndex(e);
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                mScrollPointerId = MotionEventCompat.getPointerId(e, 0);
                mInitialTouchX = mLastTouchX = (int) (e.dragInfo.updatePoint.getPointX() + 0.5f);
                mInitialTouchY = mLastTouchY = (int) (e.dragInfo.updatePoint.getPointY() + 0.5f);
                if (mScrollState == SCROLL_STATE_SETTLING) {
                    setScrollState(SCROLL_STATE_DRAGGING);
                }
                break;
            case MotionEventCompat.ACTION_POINTER_DOWN:
                mScrollPointerId = MotionEventCompat.getPointerId(e, actionIndex);
                mInitialTouchX = mLastTouchX = (int) (MotionEventCompat.getX(e, actionIndex) + 0.5f);
                mInitialTouchY = mLastTouchY = (int) (MotionEventCompat.getY(e, actionIndex) + 0.5f);
                break;
            case MotionEvent.ACTION_MOVE: {
                final int index = MotionEventCompat.findPointerIndex(e, mScrollPointerId);
                if (index < 0) {
                    return false;
                }
                final int x = (int) (MotionEventCompat.getX(e, index) + 0.5f);
                final int y = (int) (MotionEventCompat.getY(e, index) + 0.5f);
                if (mScrollState != SCROLL_STATE_DRAGGING) {
                    final int dx = x - mInitialTouchX;
                    final int dy = y - mInitialTouchY;
                    boolean startScroll = false;
                    if (canScrollHorizontally && Math.abs(dx) > mTouchSlop) {
                        mLastTouchX = mInitialTouchX + mTouchSlop * (dx < 0 ? -1 : 1);
                        startScroll = true;
                    }
                    if (canScrollVertically && Math.abs(dy) > mTouchSlop) {
                        mLastTouchY = mInitialTouchY + mTouchSlop * (dy < 0 ? -1 : 1);
                        startScroll = true;
                    }
                    if (startScroll) {
                        setScrollState(SCROLL_STATE_DRAGGING);
                    }
                }
            }
            break;
            case MotionEventCompat.ACTION_POINTER_UP: {
                onPointerUp(e);
            }
            break;
            case MotionEvent.ACTION_UP: {
                mVelocityTracker.clear();
            }
            break;
        }
        return mScrollState == SCROLL_STATE_DRAGGING;
    }

    @Override
    public boolean onTouchEvent(MotionEvent e) {
        final boolean canScrollHorizontally = mLayout.canScrollHorizontally();
        final boolean canScrollVertically = mLayout.canScrollVertically();
        if (mVelocityTracker == null) {
            mVelocityTracker = VelocityTracker.obtain();
        }
        mVelocityTracker.addMovement(e);
        final int action = MotionEventCompat.getActionMasked(e);
        final int actionIndex = MotionEventCompat.getActionIndex(e);
        switch (action) {
            case MotionEvent.ACTION_DOWN: {
                mScrollPointerId = MotionEventCompat.getPointerId(e, 0);
                mInitialTouchX = mLastTouchX = (int) (e.dragInfo.updatePoint.getPointX() + 0.5f);
                mInitialTouchY = mLastTouchY = (int) (e.dragInfo.updatePoint.getPointY() + 0.5f);
            }
            break;
            case MotionEventCompat.ACTION_POINTER_DOWN: {
                mScrollPointerId = MotionEventCompat.getPointerId(e, actionIndex);
                mInitialTouchX = mLastTouchX = (int) (MotionEventCompat.getX(e, actionIndex) + 0.5f);
                mInitialTouchY = mLastTouchY = (int) (MotionEventCompat.getY(e, actionIndex) + 0.5f);
            }
            break;
            case MotionEvent.ACTION_MOVE: {
                final int index = MotionEventCompat.findPointerIndex(e, mScrollPointerId);
                if (index < 0) {
                    return false;
                }
                final int x = (int) (MotionEventCompat.getX(e, index) + 0.5f);
                final int y = (int) (MotionEventCompat.getY(e, index) + 0.5f);
                if (mScrollState != SCROLL_STATE_DRAGGING) {
                    final int dx = x - mInitialTouchX;
                    final int dy = y - mInitialTouchY;
                    boolean startScroll = false;
                    if (canScrollHorizontally && Math.abs(dx) > mTouchSlop) {
                        mLastTouchX = mInitialTouchX + mTouchSlop * (dx < 0 ? -1 : 1);
                        startScroll = true;
                    }
                    if (canScrollVertically && Math.abs(dy) > mTouchSlop) {
                        mLastTouchY = mInitialTouchY + mTouchSlop * (dy < 0 ? -1 : 1);
                        startScroll = true;
                    }
                    if (startScroll) {
                        setScrollState(SCROLL_STATE_DRAGGING);
                    }
                }
                if (mScrollState == SCROLL_STATE_DRAGGING) {
                    final int dx = x - mLastTouchX;
                    final int dy = y - mLastTouchY;
                    scrollByInternal(canScrollHorizontally ? -dx : 0,
                            canScrollVertically ? -dy : 0);
                }
                mLastTouchX = x;
                mLastTouchY = y;
            }
            break;
            case MotionEventCompat.ACTION_POINTER_UP: {
                onPointerUp(e);
            }
            break;
            case MotionEvent.ACTION_UP: {
                mVelocityTracker.computeCurrentVelocity(1000, mMaxFlingVelocity);
                final float xvel = canScrollHorizontally ?
                        -VelocityTrackerCompat.getXVelocity(mVelocityTracker, mScrollPointerId) : 0;
                final float yvel = canScrollVertically ?
                        -VelocityTrackerCompat.getYVelocity(mVelocityTracker, mScrollPointerId) : 0;
                if (xvel != 0 && canScrollHorizontally || yvel != 0 && canScrollVertically) {
                    fling((int) xvel, (int) yvel);
                } else {
                    setScrollState(SCROLL_STATE_IDLE);
                }
                mVelocityTracker.clear();
                releaseGlows();
            }
            break;
        }
        return true;
    }

    private void onPointerUp(MotionEvent e) {
        final int actionIndex = MotionEventCompat.getActionIndex(e);
        if (MotionEventCompat.getPointerId(e, actionIndex) == mScrollPointerId) {
            // Pick a new pointer to pick up the slack.
            final int newIndex = actionIndex == 0 ? 1 : 0;
            mScrollPointerId = MotionEventCompat.getPointerId(e, newIndex);
            mInitialTouchX = mLastTouchX = (int) (MotionEventCompat.getX(e, newIndex) + 0.5f);
            mInitialTouchY = mLastTouchY = (int) (MotionEventCompat.getY(e, newIndex) + 0.5f);
        }
    }

    @Override
    protected void onMeasure(int widthSpec, int heightSpec) {
        if (mAdapterUpdateDuringMeasure) {
            mEatRequestLayout = true;
            updateChildViews();
            mEatRequestLayout = false;
        }
        final int widthMode = EstimateSpec.getMode(widthSpec);
        final int heightMode = EstimateSpec.getMode(heightSpec);
        final int widthSize = EstimateSpec.getSize(widthSpec);
        final int heightSize = EstimateSpec.getSize(heightSpec);
        if (mLeftGlow != null) mLeftGlow.setSize(heightSize, widthSize);
        if (mTopGlow != null) mTopGlow.setSize(widthSize, heightSize);
        if (mRightGlow != null) mRightGlow.setSize(heightSize, widthSize);
        if (mBottomGlow != null) mBottomGlow.setSize(widthSize, heightSize);
        setMeasuredDimension(widthSize, heightSize);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        if (mAdapter == null) {
            return;
        }
        mEatRequestLayout = true;
        mLayout.layoutChildren(mAdapter, mRecycler);
        mEatRequestLayout = false;
        mFirstLayoutComplete = true;
    }

    @Override
    public void requestLayout() {
        if (!mEatRequestLayout) {
            super.requestLayout();
        }
    }

    @Override
    public void draw(Canvas c) {
        // super.draw(c);
        final int count = mItemDecorations.size();
        for (int i = 0; i < count; i++) {
            mItemDecorations.get(i).onDrawOver(c);
        }
        boolean needsInvalidate = false;
        if (mLeftGlow != null && !mLeftGlow.isFinished()) {
            final int restore = c.save();
            c.rotate(270, 0, 0);
            c.translate(-getHeight() + getPaddingTop(), 0);
            needsInvalidate |= mLeftGlow != null && mLeftGlow.draw(c);
            c.restoreToCount(restore);
        }
        if (mTopGlow != null && !mTopGlow.isFinished()) {
            c.translate(getPaddingLeft(), getPaddingTop());
            needsInvalidate |= mTopGlow != null && mTopGlow.draw(c);
        }
        if (mRightGlow != null && !mRightGlow.isFinished()) {
            final int restore = c.save();
            final int width = getWidth();
            c.rotate(90, 0, 0);
            c.translate(-getPaddingTop(), -width);
            needsInvalidate |= mRightGlow != null && mRightGlow.draw(c);
            c.restoreToCount(restore);
        }
        if (mBottomGlow != null && !mBottomGlow.isFinished()) {
            final int restore = c.save();
            c.rotate(180, 0, 0);
            c.translate(-getWidth() + getPaddingLeft(), -getHeight() + getPaddingTop());
            needsInvalidate |= mBottomGlow != null && mBottomGlow.draw(c);
            c.restoreToCount(restore);
        }
        if (needsInvalidate) {
            postInvalidateOnAnimation(this);
        }
    }

    @Override
    public void onDraw(Canvas c) {
        //super.onDraw(c);
        final int count = mItemDecorations.size();
        for (int i = 0; i < count; i++) {
            mItemDecorations.get(i).onDraw(c);
        }
    }

    public void layoutDecoratedWithMargins(Component child, int left, int top, int right, int bottom) {
        final Rect insets = ((LayoutParams) child.getLayoutConfig()).mDecorInsets;
        child.setLeft(left + insets.left);
        child.setTop(top + insets.top);
        child.setRight(right - insets.right);
        child.setBottom(bottom - insets.bottom);
    }

    @Override
    protected boolean checkLayoutParams(ComponentContainer.LayoutConfig p) {
        return p instanceof LayoutParams && mLayout.checkLayoutParams((LayoutParams) p);
    }

    @Override
    protected ComponentContainer.LayoutConfig generateDefaultLayoutParams() {
        if (mLayout == null) {
            throw new IllegalStateException("RecyclerContainer has no layout strategy");
        }

        return mLayout.generateDefaultLayoutParams();
    }

    @Override
    public ComponentContainer.LayoutConfig generateLayoutParams(AttrSet attrs) {
        if (mLayout == null) {
            throw new IllegalStateException("RecyclerContainer has no layout strategy");
        }
        return mLayout.generateLayoutParams(getContext(), attrs);
    }

    @Override
    protected ComponentContainer.LayoutConfig generateLayoutParams(ComponentContainer.LayoutConfig p) {
        if (mLayout == null) {
            throw new IllegalStateException("RecyclerContainer has no layout strategy");
        }
        return mLayout.generateLayoutParams(p);
    }

    void updateChildViews() {
        final int opCount = mPendingUpdates.size();
        for (int i = 0; i < opCount; i++) {
            final UpdateOp op = mPendingUpdates.get(i);
            switch (op.cmd) {
                case UpdateOp.ADD:
                    mRecycler.offsetPositionRecordsForInsert(op.positionStart, op.itemCount);
                    break;
                case UpdateOp.REMOVE:
                    mRecycler.offsetPositionRecordsForRemove(op.positionStart, op.itemCount, true);
                    break;
                case UpdateOp.UPDATE:
                    markViewRangeDirty(op.positionStart, op.itemCount);
                    break;
            }
            recycleUpdateOp(op);
        }
        mPendingUpdates.clear();
    }

    /**
     * Rebind existing views for the given range, or create as needed. Reposition/rearrange any
     * child views as appropriate and recycle/regenerate views around the range as necessary.
     *
     * @param positionStart Adapter position to start at
     * @param itemCount     Number of views that must explicitly be rebound
     */
    void markViewRangeDirty(int positionStart, int itemCount) {
        final int count = getViewHolderCount();
        final int positionEnd = positionStart + itemCount;
        for (int i = 0; i < count; i++) {
            final ViewHolder holder = mAttachedViewsByPosition.valueAt(i);
            final int position = holder.getPosition();
            if (position >= positionStart && position < positionEnd) {
                holder.mIsDirty = true;
            }
        }
    }

    /**
     * Mark all known views as dirty and in need of rebinding data.
     */
    void markKnownViewsDirty() {
        final int count = getViewHolderCount();
        for (int i = 0; i < count; i++) {
            final ViewHolder holder = mAttachedViewsByPosition.valueAt(i);
            holder.mIsDirty = true;
        }
    }

    /**
     * Schedule an update of data from the adapter to occur on the next frame.
     * On newer platform versions this happens via the postOnAnimation mechanism and RecyclerContainer
     * attempts to avoid relayouts if possible.
     * On older platform versions the RecyclerContainer requests a layout the same way ListView does.
     *
     * @param op UpdateOp
     */
    void postAdapterUpdate(UpdateOp op) {
        mPendingUpdates.add(op);
        if (mPendingUpdates.size() == 1) {
            if (mPostUpdatesOnAnimation && mHasFixedSize && mIsAttached) {
                postOnAnimation(this, mUpdateChildViewsRunnable);
            } else {
                mAdapterUpdateDuringMeasure = true;
                requestLayout();
            }
        }
    }

    ViewHolder getChildViewHolder(Component child) {
        if (child == null) {
            return null;
        }
        return ((ViewHolder) child.getTag());
    }

    public ViewHolder findViewHolderForPosition(int position) {
        LogUtil.error(this.getClass().getSimpleName(), " wwwww position " + position + "" +
                " mAttachedViewsByPosition size " + mAttachedViewsByPosition.size() +
                " viewholder " + (mAttachedViewsByPosition.get(position) == null ? "null" : "not null"));
        return mAttachedViewsByPosition.get(position);
    }

    public ViewHolder findViewHolderForId(long id) {
        return mAttachedViewsById.get(id);
    }

    public ViewHolder getViewHolderForChildAt(int childIndex) {
        return getChildViewHolder(getChildAt(childIndex));
    }

    public int getViewHolderCount() {
        return mAttachedViewsByPosition.size();
    }

    public ViewHolder getViewHolderAt(int holderIndex) {
        return mAttachedViewsByPosition.valueAt(holderIndex);
    }

    @Override
    public void addView(Component child, int index, ComponentContainer.LayoutConfig lp) {
        super.addView(child, index, lp);
        // Support lib version can't use onViewAdded/onViewRemoved.
        final ViewHolder holder = getChildViewHolder(child);
        if (holder == null) {
            throw new IllegalArgumentException("No ViewHolder specified for child view " + child);
        }
        mAttachedViewsByPosition.put(holder.getPosition(), holder);
    }

    @Override
    public void removeView(Component view) {
        final ViewHolder holder = getChildViewHolder(view);
        super.removeView(view);
        final int index = mAttachedViewsByPosition.indexOfValue(holder);
        if (index >= 0) {
            mAttachedViewsByPosition.removeAt(index);
        }
    }

    @Override
    public void removeViewAt(int index) {
        final ViewHolder holder = getChildViewHolder(getChildAt(index));
        super.removeViewAt(index);
        final int posIndex = mAttachedViewsByPosition.indexOfValue(holder);
        if (posIndex >= 0) {
            mAttachedViewsByPosition.removeAt(posIndex);
        }
    }

    /**
     * Offset the bounds of all child views by <code>dy</code> pixels.
     * Useful for implementing simple scrolling in {link LayoutManager LayoutManagers}.
     *
     * @param dy Vertical pixel offset to apply to the bounds of all child views
     */
    public void offsetChildrenVertical(int dy) {
        final int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
            getChildAt(i).setTranslationY(dy);
            LogUtil.error(TAG, "getChildAt  " + i + " dy " + dy);
        }
    }

    /**
     * Offset the bounds of all child views by <code>dy</code> pixels.
     * Useful for implementing simple scrolling in {link LayoutManager LayoutManagers}.
     *
     * @param dx Horizontal pixel offset to apply to the bounds of all child views
     */
    public void offsetChildrenHorizontal(int dx) {
        final int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
            getChildAt(i).setTranslationX(dx);
        }
    }

    public Component findContainingItemView(Component view) {
        ComponentParent parent;
        for(parent = view.getComponentParent(); parent != null && parent != this && parent instanceof Component; parent = view.getComponentParent()) {
            view = (Component) parent;
        }
        return parent == this ? view : null;
    }

    RecyclerContainer.ViewHolder findViewHolderForPosition(int position, boolean checkNewPosition) {
        int childCount = this.mChildHelper.getUnfilteredChildCount();
        RecyclerContainer.ViewHolder hidden = null;

        for(int i = 0; i < childCount; ++i) {
            RecyclerContainer.ViewHolder holder = getChildViewHolderInt(this.mChildHelper.getUnfilteredChildAt(i));
            if (holder != null && !holder.isRemoved()) {
                if (checkNewPosition) {
                    if (holder.mPosition != position) {
                        continue;
                    }
                } else if (holder.getLayoutPosition() != position) {
                    continue;
                }

                if (!this.mChildHelper.isHidden(holder.itemView)) {
                    return holder;
                }
                hidden = holder;
            }
        }
        return hidden;
    }

    public RecyclerContainer.ViewHolder findViewHolderForItemId(long id) {
        if (this.mAdapter != null && this.mAdapter.hasStableIds()) {
            int childCount = this.mChildHelper.getUnfilteredChildCount();
            RecyclerContainer.ViewHolder hidden = null;

            for(int i = 0; i < childCount; ++i) {
                RecyclerContainer.ViewHolder holder = getChildViewHolderInt(this.mChildHelper.getUnfilteredChildAt(i));
                if (holder != null && !holder.isRemoved() && holder.getItemId() == id) {
                    if (!this.mChildHelper.isHidden(holder.itemView)) {
                        return holder;
                    }
                    hidden = holder;
                }
            }
            return hidden;
        } else {
            return null;
        }
    }

    public Component findChildViewUnder(float x, float y) {
        int count = this.mChildHelper.getChildCount();

        for(int i = count - 1; i >= 0; --i) {
            Component child = this.mChildHelper.getChildAt(i);
            float translationX = child.getTranslationX();
            float translationY = child.getTranslationY();
            if (x >= (float)child.getLeft() + translationX && x <= (float)child.getRight() + translationX && y >= (float)child.getTop() + translationY && y <= (float)child.getBottom() + translationY) {
                return child;
            }
        }
        return null;
    }

    void markKnownViewsInvalid() {
        int childCount = this.mChildHelper.getUnfilteredChildCount();

        for(int i = 0; i < childCount; ++i) {
            RecyclerContainer.ViewHolder holder = getChildViewHolderInt(this.mChildHelper.getUnfilteredChildAt(i));
            if (holder != null && !holder.shouldIgnore()) {
                holder.addFlags(6);
            }
        }
        this.markItemDecorInsetsDirty();
        this.mRecycler.markKnownViewsInvalid();
    }

    void viewRangeUpdate(int positionStart, int itemCount, Object payload) {
        int childCount = this.mChildHelper.getUnfilteredChildCount();
        int positionEnd = positionStart + itemCount;

        for(int i = 0; i < childCount; ++i) {
            Component child = this.mChildHelper.getUnfilteredChildAt(i);
            RecyclerContainer.ViewHolder holder = getChildViewHolderInt(child);
            if (holder != null && !holder.shouldIgnore() && holder.mPosition >= positionStart && holder.mPosition < positionEnd) {
                holder.addFlags(2);
                holder.addChangePayload(payload);
                ((RecyclerContainer.LayoutParams)child.getLayoutConfig()).mInsetsDirty = true;
            }
        }
        this.mRecycler.viewRangeUpdate(positionStart, itemCount);
    }

    void offsetPositionRecordsForMove(int from, int to) {
        int childCount = this.mChildHelper.getUnfilteredChildCount();
        int start;
        int end;
        byte inBetweenOffset;
        if (from < to) {
            start = from;
            end = to;
            inBetweenOffset = -1;
        } else {
            start = to;
            end = from;
            inBetweenOffset = 1;
        }

        for(int i = 0; i < childCount; ++i) {
            RecyclerContainer.ViewHolder holder = getChildViewHolderInt(this.mChildHelper.getUnfilteredChildAt(i));
            if (holder != null && holder.mPosition >= start && holder.mPosition <= end) {
                if (holder.mPosition == from) {
                    holder.offsetPosition(to - from, false);
                } else {
                    holder.offsetPosition(inBetweenOffset, false);
                }
            }
        }
        this.mRecycler.offsetPositionRecordsForMove(from, to);
        this.requestLayout();
    }

    void offsetPositionRecordsForInsert(int positionStart, int itemCount) {
        int childCount = this.mChildHelper.getUnfilteredChildCount();
        for(int i = 0; i < childCount; ++i) {
            RecyclerContainer.ViewHolder holder = getChildViewHolderInt(this.mChildHelper.getUnfilteredChildAt(i));
            if (holder != null && !holder.shouldIgnore() && holder.mPosition >= positionStart) {
                holder.offsetPosition(itemCount, false);
            }
        }
        this.mRecycler.offsetPositionRecordsForInsert(positionStart, itemCount);
        this.requestLayout();
    }

    void saveOldPositions() {
        int childCount = this.mChildHelper.getUnfilteredChildCount();
        for(int i = 0; i < childCount; ++i) {
            RecyclerContainer.ViewHolder holder = getChildViewHolderInt(this.mChildHelper.getUnfilteredChildAt(i));
            if (!holder.shouldIgnore()) {
                holder.saveOldPosition();
            }
        }
    }

    void clearOldPositions() {
        int childCount = this.mChildHelper.getUnfilteredChildCount();
        for(int i = 0; i < childCount; ++i) {
            RecyclerContainer.ViewHolder holder = getChildViewHolderInt(this.mChildHelper.getUnfilteredChildAt(i));
            if (!holder.shouldIgnore()) {
                holder.clearOldPosition();
            }
        }
        this.mRecycler.clearOldPositions();
    }

    void markItemDecorInsetsDirty() {
        int childCount = this.mChildHelper.getUnfilteredChildCount();

        for(int i = 0; i < childCount; ++i) {
            Component child = this.mChildHelper.getUnfilteredChildAt(i);
            ((RecyclerContainer.LayoutParams)child.getLayoutConfig()).mInsetsDirty = true;
        }
        this.mRecycler.markItemDecorInsetsDirty();
    }

    private boolean hasUpdatedView() {
        int childCount = this.mChildHelper.getChildCount();
        for(int i = 0; i < childCount; ++i) {
            RecyclerContainer.ViewHolder holder = getChildViewHolderInt(this.mChildHelper.getChildAt(i));
            if (holder != null && !holder.shouldIgnore() && holder.isUpdated()) {
                return true;
            }
        }
        return false;
    }


    void defaultOnMeasure(int widthSpec, int heightSpec) {
        int width = RecyclerContainer.LayoutManager.chooseSize(widthSpec, this.getPaddingLeft() + this.getPaddingRight(), EstimateSpec.getSize(widthSpec));
        int height = RecyclerContainer.LayoutManager.chooseSize(heightSpec, this.getPaddingTop() + this.getPaddingBottom(), EstimateSpec.getSize(heightSpec));
        this.setMeasuredDimension(width, height);
    }

    void removeAndRecycleViews() {
        if (this.mItemAnimator != null) {
            this.mItemAnimator.endAnimations();
        }
        if (this.mLayout != null) {
            this.mLayout.removeAndRecycleAllViews(this.mRecycler);
            this.mLayout.removeAndRecycleScrapInt(this.mRecycler);
        }
        this.mRecycler.clear();
    }

    public abstract static class ViewCacheExtension {
        public ViewCacheExtension() {
        }

        public abstract Component getViewForPositionAndType(RecyclerContainer.Recycler var1, int var2, int var3);
    }

    public void setItemViewCacheSize(int size) {
        this.mRecycler.setViewCacheSize(size);
    }

    public void setViewCacheExtension(RecyclerContainer.ViewCacheExtension extension) {
        this.mRecycler.setViewCacheExtension(extension);
    }

    interface ProcessCallback {
        void processDisappeared(ViewHolder var1, RecyclerContainer.ItemAnimator.ItemHolderInfo preLayoutInfo, RecyclerContainer.ItemAnimator.ItemHolderInfo postLayoutInfo);

        void processAppeared(ViewHolder var1, RecyclerContainer.ItemAnimator.ItemHolderInfo preLayoutInfo, RecyclerContainer.ItemAnimator.ItemHolderInfo postLayoutInfo);

        void processPersistent(ViewHolder var1, RecyclerContainer.ItemAnimator.ItemHolderInfo preLayoutInfo, RecyclerContainer.ItemAnimator.ItemHolderInfo postLayoutInfo);

        void unused(ViewHolder var1);
    }

    private void dispatchLayoutStep2() {
        this.startInterceptRequestLayout();
        this.mLayout.layoutChildren(this.mAdapter, this.mRecycler);
        this.stopInterceptRequestLayout(false);
    }

    public void addOnChildAttachStateChangeListener(RecyclerContainer.OnChildAttachStateChangeListener listener) {
        if (this.mOnChildAttachStateListeners == null) {
            this.mOnChildAttachStateListeners = new ArrayList();
        }

        this.mOnChildAttachStateListeners.add(listener);
    }

    public void removeOnChildAttachStateChangeListener(RecyclerContainer.OnChildAttachStateChangeListener listener) {
        if (this.mOnChildAttachStateListeners != null) {
            this.mOnChildAttachStateListeners.remove(listener);
        }
    }

    public void clearOnChildAttachStateChangeListeners() {
        if (this.mOnChildAttachStateListeners != null) {
            this.mOnChildAttachStateListeners.clear();
        }
    }

    private Rect getItemDecorInsetsForChild(Component child) {
        RecyclerContainer.LayoutParams lp = (RecyclerContainer.LayoutParams)child.getLayoutConfig();
        if (!lp.mInsetsDirty) {
            return lp.mDecorInsets;
        }
        else {
            Rect insets = lp.mDecorInsets;
            insets.set(0, 0, 0, 0);
            int decorCount = this.mItemDecorations.size();
            for (int i = 0; i < decorCount; ++i) {
                this.mTempRect.set(0, 0, 0, 0);
                ((RecyclerContainer.ItemDecoration) this.mItemDecorations.get(i)).getItemOffsets(this.mTempRect, i);
                insets.left += this.mTempRect.left;
                insets.top += this.mTempRect.top;
                insets.right += this.mTempRect.right;
                insets.bottom += this.mTempRect.bottom;
            }
            lp.mInsetsDirty = false;
            return insets;
        }
    }

    static RecyclerContainer.ViewHolder getChildViewHolderInt(Component child) {
        return child == null ? null : (RecyclerContainer.ViewHolder)child.getTag();
    }

    String exceptionLabel() {
        return " " + super.toString() + ", adapter:" + this.mAdapter + ", layout:" + this.mLayout + ", context:" + this.getContext();
    }

    boolean canReuseUpdatedViewHolder(RecyclerContainer.ViewHolder viewHolder) {
        return this.mItemAnimator == null || this.mItemAnimator.canReuseUpdatedViewHolder(viewHolder, viewHolder.getUnmodifiedPayloads());
    }

    private void animateChange(RecyclerContainer.ViewHolder oldHolder, RecyclerContainer.ViewHolder newHolder,
                               RecyclerContainer.ItemAnimator.ItemHolderInfo preInfo,
                               RecyclerContainer.ItemAnimator.ItemHolderInfo postInfo, boolean oldHolderDisappearing, boolean newHolderDisappearing) {
        oldHolder.setIsRecyclable(false);
        if (oldHolderDisappearing) {
            this.addAnimatingView(oldHolder);
        }

        if (oldHolder != newHolder) {
            if (newHolderDisappearing) {
                this.addAnimatingView(newHolder);
            }

            oldHolder.mShadowedHolder = newHolder;
            this.addAnimatingView(oldHolder);
            this.mRecycler.unscrapView(oldHolder);
            newHolder.setIsRecyclable(false);
            newHolder.mShadowingHolder = oldHolder;
        }

        if (this.mItemAnimator.animateChange(oldHolder, newHolder, preInfo, postInfo)) {
            this.postAnimationRunner();
        }
    }

    private void addAnimatingView(RecyclerContainer.ViewHolder viewHolder) {
        Component view = viewHolder.itemView;
        boolean alreadyParented = view.getComponentParent() == this;
        this.mRecycler.unscrapView(this.getChildViewHolder(view));
        if (viewHolder.isTmpDetached()) {
            this.mChildHelper.attachViewToParent(view, -1, view.getLayoutConfig(), true);
        } else if (!alreadyParented) {
            this.mChildHelper.addView(view, true);
        } else {
            this.mChildHelper.hide(view);
        }
    }

    boolean removeAnimatingView(Component view) {
        this.startInterceptRequestLayout();
        boolean removed = this.mChildHelper.removeViewIfHidden(view);
        if (removed) {
            RecyclerContainer.ViewHolder viewHolder = getChildViewHolderInt(view);
            this.mRecycler.unscrapView(viewHolder);
            this.mRecycler.recycleViewHolderInternal(viewHolder);
        }
        this.stopInterceptRequestLayout(!removed);
        return removed;
    }

    void startInterceptRequestLayout() {
        ++this.mInterceptRequestLayoutDepth;
        if (this.mInterceptRequestLayoutDepth == 1 && !this.mLayoutFrozen) {
            this.mLayoutWasDefered = false;
        }
    }

    void stopInterceptRequestLayout(boolean performLayoutChildren) {
        if (this.mInterceptRequestLayoutDepth < 1) {
            this.mInterceptRequestLayoutDepth = 1;
        }
        if (!performLayoutChildren && !this.mLayoutFrozen) {
            this.mLayoutWasDefered = false;
        }
        if (this.mInterceptRequestLayoutDepth == 1) {
            if (performLayoutChildren && this.mLayoutWasDefered && !this.mLayoutFrozen && this.mLayout != null && this.mAdapter != null) {
                this.dispatchLayout();
            }

            if (!this.mLayoutFrozen) {
                this.mLayoutWasDefered = false;
            }
        }
        --this.mInterceptRequestLayoutDepth;
    }

    protected void removeDetachedView(Component child, boolean animate) {
        RecyclerContainer.ViewHolder vh = getChildViewHolderInt(child);
        if (vh != null) {
            if (vh.isTmpDetached()) {
                vh.clearTmpDetachFlag();
            } else if (!vh.shouldIgnore()) {
                throw new IllegalArgumentException("Called removeDetachedView with a view which is not flagged as tmp detached." + vh + this.exceptionLabel());
            }
        }
        this.dispatchChildDetached(child);
    }

    void dispatchChildDetached(Component child) {
        RecyclerContainer.ViewHolder viewHolder = getChildViewHolderInt(child);
        this.onChildDetachedFromWindow(child);
        if (this.mAdapter != null && viewHolder != null) {
            this.mAdapter.onViewDetachedFromWindow(viewHolder);
        }

        if (this.mOnChildAttachStateListeners != null) {
            int cnt = this.mOnChildAttachStateListeners.size();

            for(int i = cnt - 1; i >= 0; --i) {
                ((RecyclerContainer.OnChildAttachStateChangeListener)this.mOnChildAttachStateListeners.get(i)).onChildViewDetachedFromWindow(child);
            }
        }
    }

    void dispatchChildAttached(Component child) {
        RecyclerContainer.ViewHolder viewHolder = getChildViewHolderInt(child);
        this.onChildAttachedToWindow(child);
        if (this.mAdapter != null && viewHolder != null) {
            this.mAdapter.onViewAttachedToWindow(viewHolder);
        }

        if (this.mOnChildAttachStateListeners != null) {
            int cnt = this.mOnChildAttachStateListeners.size();

            for(int i = cnt - 1; i >= 0; --i) {
                ((RecyclerContainer.OnChildAttachStateChangeListener)this.mOnChildAttachStateListeners.get(i)).onChildViewAttachedToWindow(child);
            }
        }
    }

    public void onChildAttachedToWindow(Component child) {
    }

    public void onChildDetachedFromWindow(Component child) {
    }

    public interface OnChildAttachStateChangeListener {
        void onChildViewAttachedToWindow(Component var1);

        void onChildViewDetachedFromWindow(Component var1);
    }

    public boolean isLayoutFrozen() {
        return this.mLayoutFrozen;
    }

    public interface LayoutPrefetchRegistry {
        void addPosition(int var1, int var2);
    }

    void postAnimationRunner() {
        if (!this.mPostedAnimatorRunner && this.mIsAttached) {
            this.postOnAnimation(this, this.mItemAnimatorRunner);
            this.mPostedAnimatorRunner = true;
        }

    }

    void dispatchLayout() {
        if (this.mAdapter == null) {
            LogUtil.error("RecyclerView", "No adapter attached; skipping layout");
        } else if (this.mLayout == null) {
            LogUtil.error("RecyclerView", "No layout manager attached; skipping layout");
        } else {
            if (this.mLayout.getWidth() == this.getWidth() && this.mLayout.getHeight() == this.getHeight()) {
                this.mLayout.setExactMeasureSpecsFrom(this);
            } else {
                this.mLayout.setExactMeasureSpecsFrom(this);
                this.dispatchLayoutStep2();
            }
            //this.dispatchLayoutStep3();
        }
    }

    static class LayoutPrefetchRegistryImpl implements LayoutPrefetchRegistry {
        int mPrefetchDx;
        int mPrefetchDy;
        int[] mPrefetchArray;
        int mCount;

        LayoutPrefetchRegistryImpl() {
        }

        void setPrefetchVector(int dx, int dy) {
            this.mPrefetchDx = dx;
            this.mPrefetchDy = dy;
        }

        public void addPosition(int layoutPosition, int pixelDistance) {
            if (layoutPosition < 0) {
                throw new IllegalArgumentException("Layout positions must be non-negative");
            } else if (pixelDistance < 0) {
                throw new IllegalArgumentException("Pixel distance must be non-negative");
            } else {
                int storagePosition = this.mCount * 2;
                if (this.mPrefetchArray == null) {
                    this.mPrefetchArray = new int[4];
                    Arrays.fill(this.mPrefetchArray, -1);
                } else if (storagePosition >= this.mPrefetchArray.length) {
                    int[] oldArray = this.mPrefetchArray;
                    this.mPrefetchArray = new int[storagePosition * 2];
                    System.arraycopy(oldArray, 0, this.mPrefetchArray, 0, oldArray.length);
                }

                this.mPrefetchArray[storagePosition] = layoutPosition;
                this.mPrefetchArray[storagePosition + 1] = pixelDistance;
                ++this.mCount;
            }
        }

        boolean lastPrefetchIncludedPosition(int position) {
            if (this.mPrefetchArray != null) {
                int count = this.mCount * 2;

                for(int i = 0; i < count; i += 2) {
                    if (this.mPrefetchArray[i] == position) {
                        return true;
                    }
                }
            }
            return false;
        }

        void clearPrefetchPositions() {
            if (this.mPrefetchArray != null) {
                Arrays.fill(this.mPrefetchArray, -1);
            }
            this.mCount = 0;
        }
    }

    static class Task {
        public boolean immediate;
        public int viewVelocity;
        public int distanceToItem;
        public RecyclerContainer view;
        public int position;

        Task() {
        }

        public void clear() {
            this.immediate = false;
            this.viewVelocity = 0;
            this.distanceToItem = 0;
            this.view = null;
            this.position = 0;
        }
    }

    public abstract static class ItemAnimator {
        public static final int FLAG_CHANGED = 2;
        public static final int FLAG_REMOVED = 8;
        public static final int FLAG_INVALIDATED = 4;
        public static final int FLAG_MOVED = 2048;
        public static final int FLAG_APPEARED_IN_PRE_LAYOUT = 4096;
        private RecyclerContainer.ItemAnimator.ItemAnimatorListener mListener = null;
        private ArrayList<RecyclerContainer.ItemAnimator.ItemAnimatorFinishedListener> mFinishedListeners = new ArrayList();
        private long mAddDuration = 120L;
        private long mRemoveDuration = 120L;
        private long mMoveDuration = 250L;
        private long mChangeDuration = 250L;

        public ItemAnimator() {
        }

        public long getMoveDuration() {
            return this.mMoveDuration;
        }

        public void setMoveDuration(long moveDuration) {
            this.mMoveDuration = moveDuration;
        }

        public long getAddDuration() {
            return this.mAddDuration;
        }

        public void setAddDuration(long addDuration) {
            this.mAddDuration = addDuration;
        }

        public long getRemoveDuration() {
            return this.mRemoveDuration;
        }

        public void setRemoveDuration(long removeDuration) {
            this.mRemoveDuration = removeDuration;
        }

        public long getChangeDuration() {
            return this.mChangeDuration;
        }

        public void setChangeDuration(long changeDuration) {
            this.mChangeDuration = changeDuration;
        }

        void setListener(RecyclerContainer.ItemAnimator.ItemAnimatorListener listener) {
            this.mListener = listener;
        }

        public RecyclerContainer.ItemAnimator.ItemHolderInfo recordPreLayoutInformation(RecyclerContainer.Adapter adapter,
                                                                                   RecyclerContainer.ViewHolder viewHolder, int changeFlags, List<Object> payloads) {
            return this.obtainHolderInfo().setFrom(viewHolder);
        }

        public RecyclerContainer.ItemAnimator.ItemHolderInfo recordPostLayoutInformation(RecyclerContainer.Adapter adapter, RecyclerContainer.ViewHolder viewHolder) {
            return this.obtainHolderInfo().setFrom(viewHolder);
        }

        public abstract boolean animateDisappearance(RecyclerContainer.ViewHolder var1, RecyclerContainer.ItemAnimator.ItemHolderInfo var2,
                                                     RecyclerContainer.ItemAnimator.ItemHolderInfo var3);

        public abstract boolean animateAppearance(RecyclerContainer.ViewHolder var1, RecyclerContainer.ItemAnimator.ItemHolderInfo var2,
                                                  RecyclerContainer.ItemAnimator.ItemHolderInfo var3);

        public abstract boolean animatePersistence(RecyclerContainer.ViewHolder var1, RecyclerContainer.ItemAnimator.ItemHolderInfo var2,
                                                   RecyclerContainer.ItemAnimator.ItemHolderInfo var3);

        public abstract boolean animateChange(RecyclerContainer.ViewHolder var1, RecyclerContainer.ViewHolder var2, RecyclerContainer.ItemAnimator.ItemHolderInfo var3,
                                              RecyclerContainer.ItemAnimator.ItemHolderInfo var4);

        public abstract void runPendingAnimations();

        public abstract void endAnimation(RecyclerContainer.ViewHolder var1);

        public abstract void endAnimations();

        public abstract boolean isRunning();

        public final void dispatchAnimationFinished(RecyclerContainer.ViewHolder viewHolder) {
            this.onAnimationFinished(viewHolder);
            if (this.mListener != null) {
                this.mListener.onAnimationFinished(viewHolder);
            }
        }

        public void onAnimationFinished(RecyclerContainer.ViewHolder viewHolder) {
        }

        public final void dispatchAnimationStarted(RecyclerContainer.ViewHolder viewHolder) {
            this.onAnimationStarted(viewHolder);
        }

        public void onAnimationStarted(RecyclerContainer.ViewHolder viewHolder) {
        }

        public final boolean isRunning(RecyclerContainer.ItemAnimator.ItemAnimatorFinishedListener listener) {
            boolean running = this.isRunning();
            if (listener != null) {
                if (!running) {
                    listener.onAnimationsFinished();
                } else {
                    this.mFinishedListeners.add(listener);
                }
            }
            return running;
        }

        public boolean canReuseUpdatedViewHolder(RecyclerContainer.ViewHolder viewHolder) {
            return true;
        }

        public boolean canReuseUpdatedViewHolder(RecyclerContainer.ViewHolder viewHolder, List<Object> payloads) {
            return this.canReuseUpdatedViewHolder(viewHolder);
        }

        public final void dispatchAnimationsFinished() {
            int count = this.mFinishedListeners.size();

            for(int i = 0; i < count; ++i) {
                ((RecyclerContainer.ItemAnimator.ItemAnimatorFinishedListener)this.mFinishedListeners.get(i)).onAnimationsFinished();
            }
            this.mFinishedListeners.clear();
        }

        public RecyclerContainer.ItemAnimator.ItemHolderInfo obtainHolderInfo() {
            return new RecyclerContainer.ItemAnimator.ItemHolderInfo();
        }

        public static class ItemHolderInfo {
            public int left;
            public int top;
            public int right;
            public int bottom;
            public int changeFlags;

            public ItemHolderInfo() {
            }

            public RecyclerContainer.ItemAnimator.ItemHolderInfo setFrom(RecyclerContainer.ViewHolder holder) {
                return this.setFrom(holder, 0);
            }

            public RecyclerContainer.ItemAnimator.ItemHolderInfo setFrom(RecyclerContainer.ViewHolder holder, int flags) {
                Component view = holder.itemView;
                this.left = view.getLeft();
                this.top = view.getTop();
                this.right = view.getRight();
                this.bottom = view.getBottom();
                return this;
            }
        }

        public interface ItemAnimatorFinishedListener {
            void onAnimationsFinished();
        }

        interface ItemAnimatorListener {
            void onAnimationFinished(RecyclerContainer.ViewHolder var1);
        }

        @Retention(RetentionPolicy.SOURCE)
        public @interface AdapterChanges {
        }
    }

    private class ItemAnimatorRestoreListener implements RecyclerContainer.ItemAnimator.ItemAnimatorListener {
        ItemAnimatorRestoreListener() {
        }

        public void onAnimationFinished(RecyclerContainer.ViewHolder item) {
            item.setIsRecyclable(true);
            if (item.mShadowedHolder != null && item.mShadowingHolder == null) {
                item.mShadowedHolder = null;
            }

            item.mShadowingHolder = null;
            if (!item.shouldBeKeptAsChild() && !RecyclerContainer.this.removeAnimatingView(item.itemView) && item.isTmpDetached()) {
                RecyclerContainer.this.removeDetachedView(item.itemView, false);
            }
        }
    }

    private class ViewFlinger implements Runnable {
        private int mLastFlingX;
        private int mLastFlingY;
        private ScrollerCompat mScroller;
        private Interpolator mInterpolator = sQuinticInterpolator;

        public ViewFlinger() {
            ScrollView m;
            mScroller = ScrollerCompat.create(getContext(), sQuinticInterpolator);
        }

        @Override
        public void run() {
            if (mScroller.computeScrollOffset()) {
                final int x = mScroller.getCurrX();
                final int y = mScroller.getCurrY();
                final int dx = x - mLastFlingX;
                final int dy = y - mLastFlingY;
                mLastFlingX = x;
                mLastFlingY = y;
                int overscrollX = 0, overscrollY = 0;
                if (dx != 0) {
                    mEatRequestLayout = true;
                    final int hresult = mLayout.scrollHorizontallyBy(dx, getAdapter(), mRecycler);
                    mEatRequestLayout = false;
                    overscrollX = dx - hresult;
                }
                if (dy != 0) {
                    mEatRequestLayout = true;
                    final int vresult = mLayout.scrollVerticallyBy(dy, getAdapter(), mRecycler);
                    mEatRequestLayout = false;
                    overscrollY = dy - vresult;
                }
                if (overscrollX != 0 || overscrollY != 0) {
                    final int vel = (int) mScroller.getCurrVelocity();
                    int velX = 0;
                    if (overscrollX != x) {
                        velX = overscrollX < 0 ? -vel : overscrollX > 0 ? vel : 0;
                    }
                    int velY = 0;
                    if (overscrollY != y) {
                        velY = overscrollY < 0 ? -vel : overscrollY > 0 ? vel : 0;
                    }
                    absorbGlows(velX, velY);
                    if ((velX != 0 || overscrollX == x || mScroller.getFinalX() == 0) &&
                            (velY != 0 || overscrollY == y || mScroller.getFinalY() == 0)) {
                        mScroller.abortAnimation();
                    }
                }
                if (mScroller.isFinished()) {
                    setScrollState(SCROLL_STATE_IDLE);
                } else {
                    postOnAnimation(RecyclerContainer.this, this);
                }
            }
        }

        public void fling(int velocityX, int velocityY) {
            setScrollState(SCROLL_STATE_SETTLING);
            mLastFlingX = mLastFlingY = 0;
            mScroller.fling(0, 0, velocityX, velocityY,
                    Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE);
            postOnAnimation(RecyclerContainer.this, this);
        }

        public void smoothScrollBy(int dx, int dy) {
            smoothScrollBy(dx, dy, 0, 0);
        }

        public void smoothScrollBy(int dx, int dy, int vx, int vy) {
            smoothScrollBy(dx, dy, computeScrollDuration(dx, dy, vx, vy));
        }

        private float distanceInfluenceForSnapDuration(float f) {
            f -= 0.5f; // center the values about 0.
            f *= 0.3f * Math.PI / 2.0f;
            return (float) Math.sin(f);
        }

        private int computeScrollDuration(int dx, int dy, int vx, int vy) {
            final int absDx = Math.abs(dx);
            final int absDy = Math.abs(dy);
            final boolean horizontal = absDx > absDy;
            final int velocity = (int) Math.sqrt(vx * vx + vy * vy);
            final int delta = (int) Math.sqrt(dx * dx + dy * dy);
            final int containerSize = horizontal ? getWidth() : getHeight();
            final int halfContainerSize = containerSize / 2;
            final float distanceRatio = Math.min(1.f, 1.f * delta / containerSize);
            final float distance = halfContainerSize + halfContainerSize *
                    distanceInfluenceForSnapDuration(distanceRatio);
            final int duration;
            if (velocity > 0) {
                duration = 4 * Math.round(1000 * Math.abs(distance / velocity));
            } else {
                duration = (int) ((((float) absDx / containerSize) + 1) * 300);
            }
            return Math.min(duration, MAX_SCROLL_DURATION);
        }

        public void smoothScrollBy(int dx, int dy, int duration) {
            smoothScrollBy(dx, dy, duration, sQuinticInterpolator);
        }

        public void smoothScrollBy(int dx, int dy, int duration, Interpolator interpolator) {
            if (mInterpolator != interpolator) {
                mInterpolator = interpolator;
                mScroller = ScrollerCompat.create(getContext(), interpolator);
            }
            setScrollState(SCROLL_STATE_SETTLING);
            mLastFlingX = mLastFlingY = 0;
            mScroller.startScroll(0, 0, dx, dy, duration);
            postOnAnimation(RecyclerContainer.this, this);
        }

        public void stop() {
            removeCallbacks(this);
        }
    }

    private class RecyclerViewDataObserver extends AdapterDataObserver {
        // These two fields act like a SparseLongArray for tracking nearby position-id mappings.
        // Values with the same index correspond to one another. mPositions is in ascending order.
        private int[] mPositions;
        private long[] mIds;
        private int mMappingSize;

        void initIdMapping() {
        }

        void clearIdMapping() {
            mPositions = null;
            mIds = null;
            mMappingSize = 0;
        }

        @Override
        public void onChanged() {
            if (mAdapter.hasStableIds()) {
            } else {
                mRecycler.onGenericDataChanged();
                markKnownViewsDirty();
                requestLayout();
            }
        }

        @Override
        public void onItemRangeChanged(int positionStart, int itemCount) {
            postAdapterUpdate(obtainUpdateOp(UpdateOp.UPDATE, positionStart, itemCount));
        }

        @Override
        public void onItemRangeInserted(int positionStart, int itemCount) {
            postAdapterUpdate(obtainUpdateOp(UpdateOp.ADD, positionStart, itemCount));
        }

        @Override
        public void onItemRangeRemoved(int positionStart, int itemCount) {
            postAdapterUpdate(obtainUpdateOp(UpdateOp.REMOVE, positionStart, itemCount));
        }
    }

    public static class RecycledViewPool {
        SparseArrayCompat<RecyclerContainer.RecycledViewPool.ScrapData> mScrap = new SparseArrayCompat<>();
        private int[] mMaxScrap;
        private static final int DEFAULT_MAX_SCRAP = 5;
        private int mAttachCount = 0;

        public void clear() {
            for(int i = 0; i < this.mScrap.size(); ++i) {
                RecyclerContainer.RecycledViewPool.ScrapData data = (RecyclerContainer.RecycledViewPool.ScrapData)this.mScrap.valueAt(i);
                data.mScrapHeap.clear();
            }
        }

        public void reset(int typeCount) {
            mScrap = new SparseArrayCompat<>();
            mMaxScrap = new int[typeCount];
            for (int i = 0; i < typeCount; i++) {
                mMaxScrap[i] = DEFAULT_MAX_SCRAP;
            }
        }

        public void setMaxRecycledViews(int viewType, int max) {
            RecyclerContainer.RecycledViewPool.ScrapData scrapData = this.getScrapDataForType(viewType);
            scrapData.mMaxScrap = max;
            ArrayList scrapHeap = scrapData.mScrapHeap;

            while(scrapHeap.size() > max) {
                scrapHeap.remove(scrapHeap.size() - 1);
            }
        }

        public int getRecycledViewCount(int viewType) {
            return this.getScrapDataForType(viewType).mScrapHeap.size();
        }

        public RecyclerContainer.ViewHolder getRecycledView(int viewType) {
            RecyclerContainer.RecycledViewPool.ScrapData scrapData = (RecyclerContainer.RecycledViewPool.ScrapData)this.mScrap.get(viewType);
            if (scrapData != null && !scrapData.mScrapHeap.isEmpty()) {
                ArrayList<RecyclerContainer.ViewHolder> scrapHeap = scrapData.mScrapHeap;
                return (RecyclerContainer.ViewHolder)scrapHeap.remove(scrapHeap.size() - 1);
            } else {
                return null;
            }
        }

        int size() {
            int count = 0;
            for(int i = 0; i < this.mScrap.size(); ++i) {
                ArrayList<RecyclerContainer.ViewHolder> viewHolders = ((RecyclerContainer.RecycledViewPool.ScrapData)this.mScrap.get(i)).mScrapHeap;
                if (viewHolders != null) {
                    count += viewHolders.size();
                }
            }
            return count;
        }

        public void putRecycledView(RecyclerContainer.ViewHolder scrap) {
            int viewType = scrap.getItemViewType();
            ArrayList<RecyclerContainer.ViewHolder> scrapHeap = this.getScrapDataForType(viewType).mScrapHeap;
            if (((RecyclerContainer.RecycledViewPool.ScrapData)this.mScrap.get(viewType)).mMaxScrap > scrapHeap.size()) {
                scrap.resetInternal();
                scrapHeap.add(scrap);
            }
        }

        private RecyclerContainer.RecycledViewPool.ScrapData getScrapDataForType(int viewType) {
            RecyclerContainer.RecycledViewPool.ScrapData scrapData = (RecyclerContainer.RecycledViewPool.ScrapData)this.mScrap.get(viewType);
            if (scrapData == null) {
                scrapData = new RecyclerContainer.RecycledViewPool.ScrapData();
                this.mScrap.put(viewType, scrapData);
            }
            return scrapData;
        }

        static class ScrapData {
            final ArrayList<RecyclerContainer.ViewHolder> mScrapHeap = new ArrayList();
            int mMaxScrap = 5;
            long mCreateRunningAverageNs = 0L;
            long mBindRunningAverageNs = 0L;

            ScrapData() {
            }
        }

        long runningAverage(long oldAverage, long newValue) {
            return oldAverage == 0L ? newValue : oldAverage / 4L * 3L + newValue / 4L;
        }

        void factorInCreateTime(int viewType, long createTimeNs) {
            RecyclerContainer.RecycledViewPool.ScrapData scrapData = this.getScrapDataForType(viewType);
            scrapData.mCreateRunningAverageNs = this.runningAverage(scrapData.mCreateRunningAverageNs, createTimeNs);
        }

        void factorInBindTime(int viewType, long bindTimeNs) {
            RecyclerContainer.RecycledViewPool.ScrapData scrapData = this.getScrapDataForType(viewType);
            scrapData.mBindRunningAverageNs = this.runningAverage(scrapData.mBindRunningAverageNs, bindTimeNs);
        }

        boolean willCreateInTime(int viewType, long approxCurrentNs, long deadlineNs) {
            long expectedDurationNs = this.getScrapDataForType(viewType).mCreateRunningAverageNs;
            return expectedDurationNs == 0L || approxCurrentNs + expectedDurationNs < deadlineNs;
        }

        boolean willBindInTime(int viewType, long approxCurrentNs, long deadlineNs) {
            long expectedDurationNs = this.getScrapDataForType(viewType).mBindRunningAverageNs;
            return expectedDurationNs == 0L || approxCurrentNs + expectedDurationNs < deadlineNs;
        }

        void attach() {
            ++this.mAttachCount;
        }

        void detach() {
            --this.mAttachCount;
        }

        void onAdapterChanged(RecyclerContainer.Adapter oldAdapter, RecyclerContainer.Adapter newAdapter, boolean compatibleWithPrevious) {
            if (oldAdapter != null) {
                this.detach();
            }

            if (!compatibleWithPrevious && this.mAttachCount == 0) {
                this.clear();
            }

            if (newAdapter != null) {
                this.attach();
            }
        }
    }

    public final class Recycler {
        private final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<ViewHolder>();
        ArrayList<RecyclerContainer.ViewHolder> mChangedScrap = null;
        int mViewCacheMax;
        private int mRequestedCacheMax;
        RecyclerContainer.RecycledViewPool mRecyclerPool;
        private RecyclerContainer.ViewCacheExtension mViewCacheExtension;
        final ArrayList<RecyclerContainer.ViewHolder> mCachedViews = new ArrayList();
        private final List<RecyclerContainer.ViewHolder> mUnmodifiableAttachedScrap;

        public Recycler() {
            this.mUnmodifiableAttachedScrap = Collections.unmodifiableList(this.mAttachedScrap);
            this.mRequestedCacheMax = 2;
            this.mViewCacheMax = 2;
        }

        public void clear() {
            this.mAttachedScrap.clear();
            this.recycleAndClearCachedViews();
        }

        public void setViewCacheSize(int viewCount) {
            this.mRequestedCacheMax = viewCount;
            this.updateViewCacheSize();
        }

        void updateViewCacheSize() {
            int extraCache = RecyclerContainer.this.mLayout != null ? RecyclerContainer.this.mLayout.mPrefetchMaxCountObserved : 0;
            this.mViewCacheMax = this.mRequestedCacheMax + extraCache;

            for(int i = this.mCachedViews.size() - 1; i >= 0 && this.mCachedViews.size() > this.mViewCacheMax; --i) {
                this.recycleCachedViewAt(i);
            }
        }

        public List<RecyclerContainer.ViewHolder> getScrapList() {
            return this.mUnmodifiableAttachedScrap;
        }

        boolean validateViewHolderForOffsetPosition(RecyclerContainer.ViewHolder holder) {
            if (holder.isRemoved()) {
                return false;
            } else if (holder.mPosition >= 0 && holder.mPosition < RecyclerContainer.this.mAdapter.getItemCount()) {
                if (true) {
                    int type = RecyclerContainer.this.mAdapter.getItemViewType(holder.mPosition);
                    if (type != holder.getItemViewType()) {
                        return false;
                    }
                }

                if (RecyclerContainer.this.mAdapter.hasStableIds()) {
                    return holder.getItemId() == RecyclerContainer.this.mAdapter.getItemId(holder.mPosition);
                } else {
                    return true;
                }
            } else {
                throw new IndexOutOfBoundsException("Inconsistency detected. Invalid view holder adapter position" + holder + RecyclerContainer.this.exceptionLabel());
            }
        }

        public void recycleView(Component view) {
            RecyclerContainer.ViewHolder holder = RecyclerContainer.getChildViewHolderInt(view);

            if (holder.isTmpDetached()) {
                RecyclerContainer.this.removeDetachedView(view, false);
            }
            if (holder.isScrap()) {
                holder.unScrap();
            } else if (holder.wasReturnedFromScrap()) {
                holder.clearReturnedFromScrapFlag();
            }
            this.recycleViewHolderInternal(holder);
        }

        void recycleViewInternal(Component view) {
            this.recycleViewHolderInternal(RecyclerContainer.getChildViewHolderInt(view));
        }

        void setViewCacheExtension(RecyclerContainer.ViewCacheExtension extension) {
            this.mViewCacheExtension = extension;
        }

        void recycleAndClearCachedViews() {
            int count = this.mCachedViews.size();
            for(int i = count - 1; i >= 0; --i) {
                this.recycleCachedViewAt(i);
            }
            this.mCachedViews.clear();
        }

        void recycleCachedViewAt(int cachedViewIndex) {
            RecyclerContainer.ViewHolder viewHolder = (RecyclerContainer.ViewHolder)this.mCachedViews.get(cachedViewIndex);
            this.addViewHolderToRecycledViewPool(viewHolder, true);
            this.mCachedViews.remove(cachedViewIndex);
        }

        void recycleViewHolderInternal(RecyclerContainer.ViewHolder holder) {
            if (!holder.isScrap() && holder.itemView.getComponentParent() == null) {
                if (holder.isTmpDetached()) {
                    throw new IllegalArgumentException("Tmp detached view should be removed from RecyclerView before it can be recycled: "
                            + holder + RecyclerContainer.this.exceptionLabel());
                } else if (holder.shouldIgnore()) {
                    throw new IllegalArgumentException("Trying to recycle an ignored view holder. You should first call stopIgnoringView(view) before calling recycle."
                            + RecyclerContainer.this.exceptionLabel());
                } else {
                    boolean transientStatePreventsRecycling = holder.doesTransientStatePreventRecycling();
                    boolean forceRecycle = RecyclerContainer.this.mAdapter != null && transientStatePreventsRecycling
                            && RecyclerContainer.this.mAdapter.onFailedToRecycleView(holder);

                    boolean cached = false;
                    boolean recycled = false;
                    if (forceRecycle || holder.isRecyclable()) {
                        if (this.mViewCacheMax > 0 && !holder.hasAnyOfTheFlags(526)) {
                            int cachedViewSize = this.mCachedViews.size();
                            if (cachedViewSize >= this.mViewCacheMax && cachedViewSize > 0) {
                                this.recycleCachedViewAt(0);
                                --cachedViewSize;
                            }

                            int targetCacheIndex = cachedViewSize;
                            if (!RecyclerContainer.this.mPrefetchRegistry.lastPrefetchIncludedPosition(holder.mPosition)) {
                                int cacheIndex;
                                for(cacheIndex = cachedViewSize - 1; cacheIndex >= 0; --cacheIndex) {
                                    int cachedPos = ((RecyclerContainer.ViewHolder)this.mCachedViews.get(cacheIndex)).mPosition;
                                    if (!RecyclerContainer.this.mPrefetchRegistry.lastPrefetchIncludedPosition(cachedPos)) {
                                        break;
                                    }
                                }
                                targetCacheIndex = cacheIndex + 1;
                            }

                            this.mCachedViews.add(targetCacheIndex, holder);
                            cached = true;
                        }

                        if (!cached) {
                            this.addViewHolderToRecycledViewPool(holder, true);
                            recycled = true;
                        }
                    }

                    if (!cached && !recycled && transientStatePreventsRecycling) {
                        holder.mOwnerRecyclerView = null;
                    }
                }
            } else {
                throw new IllegalArgumentException("Scrapped or attached views may not be recycled. isScrap:" + holder.isScrap() + " isAttached:" +
                        (holder.itemView.getComponentParent() != null) + RecyclerContainer.this.exceptionLabel());
            }
        }

        void addViewHolderToRecycledViewPool(RecyclerContainer.ViewHolder holder, boolean dispatchRecycled) {
            if (holder.hasAnyOfTheFlags(16384)) {
                holder.setFlags(0, 16384);
            }
            if (dispatchRecycled) {
                this.dispatchViewRecycled(holder);
            }
            holder.mOwnerRecyclerView = null;
            this.getRecycledViewPool().putRecycledView(holder);
        }

        void quickRecycleScrapView(Component view) {
            RecyclerContainer.ViewHolder holder = RecyclerContainer.getChildViewHolderInt(view);
            holder.mScrapContainer = null;
            holder.mInChangeScrap = false;
            holder.clearReturnedFromScrapFlag();
            this.recycleViewHolderInternal(holder);
        }

        void scrapView(Component view) {
            RecyclerContainer.ViewHolder holder = RecyclerContainer.getChildViewHolderInt(view);

            if (!holder.hasAnyOfTheFlags(12) && holder.isUpdated() && !RecyclerContainer.this.canReuseUpdatedViewHolder(holder)) {
                if (this.mChangedScrap == null) {
                    this.mChangedScrap = new ArrayList();
                }

                holder.setScrapContainer(this, true);
                this.mChangedScrap.add(holder);
            } else {
                if (holder.isInvalid() && !holder.isRemoved() && !RecyclerContainer.this.mAdapter.hasStableIds()) {
                    throw new IllegalArgumentException("Called scrap view with an invalid view. Invalid views cannot be reused from scrap, " +
                            "they should rebound from recycler pool." + RecyclerContainer.this.exceptionLabel());
                }

                holder.setScrapContainer(this, false);
                this.mAttachedScrap.add(holder);
            }
        }

        void unscrapView(RecyclerContainer.ViewHolder holder) {
            if (holder.mInChangeScrap) {
                this.mChangedScrap.remove(holder);
            } else {
                this.mAttachedScrap.remove(holder);
            }

            holder.mScrapContainer = null;
            holder.mInChangeScrap = false;
            holder.clearReturnedFromScrapFlag();
        }

        int getScrapCount() {
            return this.mAttachedScrap.size();
        }

        Component getScrapViewAt(int index) {
            return ((RecyclerContainer.ViewHolder)this.mAttachedScrap.get(index)).itemView;
        }

        void clearScrap() {
            this.mAttachedScrap.clear();
            if (this.mChangedScrap != null) {
                this.mChangedScrap.clear();
            }
        }

        RecyclerContainer.ViewHolder getScrapOrCachedViewForId(long id, int type, boolean dryRun) {
            int count = this.mAttachedScrap.size();

            int i;
            for(i = count - 1; i >= 0; --i) {
                RecyclerContainer.ViewHolder holder = (RecyclerContainer.ViewHolder)this.mAttachedScrap.get(i);
                if (holder.getItemId() == id && !holder.wasReturnedFromScrap()) {
                    if (type == holder.getItemViewType()) {
                        holder.addFlags(32);
                        if (holder.isRemoved()) {
                            holder.setFlags(2, 14);
                        }
                        return holder;
                    }

                    if (!dryRun) {
                        this.mAttachedScrap.remove(i);
                        RecyclerContainer.this.removeDetachedView(holder.itemView, false);
                        this.quickRecycleScrapView(holder.itemView);
                    }
                }
            }

            i = this.mCachedViews.size();

            for(int ix = i - 1; ix >= 0; --ix) {
                RecyclerContainer.ViewHolder holderx = (RecyclerContainer.ViewHolder)this.mCachedViews.get(ix);
                if (holderx.getItemId() == id) {
                    if (type == holderx.getItemViewType()) {
                        if (!dryRun) {
                            this.mCachedViews.remove(ix);
                        }
                        return holderx;
                    }

                    if (!dryRun) {
                        this.recycleCachedViewAt(ix);
                        return null;
                    }
                }
            }
            return null;
        }

        void dispatchViewRecycled(RecyclerContainer.ViewHolder holder) {
            if (RecyclerContainer.this.mRecyclerListener != null) {
                RecyclerContainer.this.mRecyclerListener.onViewRecycled(holder);
            }

            if (RecyclerContainer.this.mAdapter != null) {
                RecyclerContainer.this.mAdapter.onViewRecycled(holder);
            }
        }

        void offsetPositionRecordsForMove(int from, int to) {
            int start;
            int end;
            byte inBetweenOffset;
            if (from < to) {
                start = from;
                end = to;
                inBetweenOffset = -1;
            } else {
                start = to;
                end = from;
                inBetweenOffset = 1;
            }

            int cachedCount = this.mCachedViews.size();

            for(int i = 0; i < cachedCount; ++i) {
                RecyclerContainer.ViewHolder holder = (RecyclerContainer.ViewHolder)this.mCachedViews.get(i);
                if (holder != null && holder.mPosition >= start && holder.mPosition <= end) {
                    if (holder.mPosition == from) {
                        holder.offsetPosition(to - from, false);
                    } else {
                        holder.offsetPosition(inBetweenOffset, false);
                    }
                }
            }
        }

        void viewRangeUpdate(int positionStart, int itemCount) {
            int positionEnd = positionStart + itemCount;
            int cachedCount = this.mCachedViews.size();

            for(int i = cachedCount - 1; i >= 0; --i) {
                RecyclerContainer.ViewHolder holder = (RecyclerContainer.ViewHolder)this.mCachedViews.get(i);
                if (holder != null) {
                    int pos = holder.mPosition;
                    if (pos >= positionStart && pos < positionEnd) {
                        holder.addFlags(2);
                        this.recycleCachedViewAt(i);
                    }
                }
            }
        }

        void markKnownViewsInvalid() {
            int cachedCount = this.mCachedViews.size();
            for(int i = 0; i < cachedCount; ++i) {
                RecyclerContainer.ViewHolder holder = (RecyclerContainer.ViewHolder)this.mCachedViews.get(i);
                if (holder != null) {
                    holder.addFlags(6);
                    holder.addChangePayload((Object)null);
                }
            }

            if (RecyclerContainer.this.mAdapter == null || !RecyclerContainer.this.mAdapter.hasStableIds()) {
                this.recycleAndClearCachedViews();
            }
        }

        void clearOldPositions() {
            int cachedCount = this.mCachedViews.size();

            int scrapCount;
            for(scrapCount = 0; scrapCount < cachedCount; ++scrapCount) {
                RecyclerContainer.ViewHolder holder = (RecyclerContainer.ViewHolder)this.mCachedViews.get(scrapCount);
                holder.clearOldPosition();
            }

            scrapCount = this.mAttachedScrap.size();

            int changedScrapCount;
            for(changedScrapCount = 0; changedScrapCount < scrapCount; ++changedScrapCount) {
                ((RecyclerContainer.ViewHolder)this.mAttachedScrap.get(changedScrapCount)).clearOldPosition();
            }

            if (this.mChangedScrap != null) {
                changedScrapCount = this.mChangedScrap.size();

                for(int i = 0; i < changedScrapCount; ++i) {
                    ((RecyclerContainer.ViewHolder)this.mChangedScrap.get(i)).clearOldPosition();
                }
            }
        }

        void markItemDecorInsetsDirty() {
            int cachedCount = this.mCachedViews.size();

            for(int i = 0; i < cachedCount; ++i) {
                RecyclerContainer.ViewHolder holder = (RecyclerContainer.ViewHolder)this.mCachedViews.get(i);
                RecyclerContainer.LayoutParams layoutParams = (RecyclerContainer.LayoutParams)holder.itemView.getLayoutConfig();
                if (layoutParams != null) {
                    layoutParams.mInsetsDirty = true;
                }
            }
        }

        void setRecycledViewPool(RecyclerContainer.RecycledViewPool pool) {
            if (this.mRecyclerPool != null) {
                this.mRecyclerPool.detach();
            }
            this.mRecyclerPool = pool;
            if (this.mRecyclerPool != null && RecyclerContainer.this.getAdapter() != null) {
                this.mRecyclerPool.attach();
            }
        }

        RecyclerContainer.RecycledViewPool getRecycledViewPool() {
            if (this.mRecyclerPool == null) {
                this.mRecyclerPool = new RecyclerContainer.RecycledViewPool();
            }
            return this.mRecyclerPool;
        }

        void onAdapterChanged(RecyclerContainer.Adapter oldAdapter, RecyclerContainer.Adapter newAdapter, boolean compatibleWithPrevious) {
            this.clear();
            this.getRecycledViewPool().onAdapterChanged(oldAdapter, newAdapter, compatibleWithPrevious);
        }

        public Component getViewForPosition(int position) {
            ViewHolder holder;
            final int type = mAdapter.getItemViewType(position);
            if (mAdapter.hasStableIds()) {
                final long id = mAdapter.getItemId(position);
                holder = getScrapViewForId(id, type);
            }
            else {
                holder = getScrapViewForPosition(position, type);
            }
            if (holder == null) {
                holder = mAdapter.createViewHolder(RecyclerContainer.this, type);
                holder.mItemViewType = type;
            }
            if (holder.mIsDirty) {
                mAdapter.bindViewHolder(holder, position);
                holder.mIsDirty = false;
                holder.mPosition = position;
            }
            ComponentContainer.LayoutConfig lp = holder.itemView.getLayoutConfig();
            if (lp == null) {
                lp = generateDefaultLayoutParams();
                holder.itemView.setLayoutConfig(lp);
            } else if (!checkLayoutParams(lp)) {
                lp = generateLayoutParams(lp);
                holder.itemView.setLayoutConfig(lp);
            }

            ((LayoutParams) lp).mViewHolder = holder;
            ((LayoutParams) lp).v2 = holder;
            ((LayoutParams) lp).l = 12;

            holder.itemView.setTag(((LayoutParams) lp).mViewHolder);

            RecyclerContainer.ViewHolder v = ((RecyclerContainer.LayoutParams) lp).mViewHolder;
            if (v == null) LogUtil.error(this.getClass().getSimpleName(), "------------------view holder null");
            return holder.itemView;
        }

        public void addDetachedScrapView(Component scrap) {
            final ViewHolder holder = getChildViewHolder(scrap);
            getRecycledViewPool().putRecycledView(holder);
            dispatchViewRecycled(holder);
        }

        public void detachAndScrapView(Component scrap) {
            if (scrap.getComponentParent() != RecyclerContainer.this) {
                throw new IllegalArgumentException("View " + scrap + " is not attached to " +
                        RecyclerContainer.this);
            }
            final ViewHolder holder = getChildViewHolder(scrap);
            holder.mIsDirty = true;
            removeView(scrap);
            getRecycledViewPool().putRecycledView(holder);
            dispatchViewRecycled(holder);
        }

        public void scrapAllViewsAttached() {
            final int count = getChildCount();
            for (int i = 0; i < count; i++) {
                final Component v = getChildAt(i);
                final ViewHolder holder = getChildViewHolder(v);
                holder.mIsDirty = true;
                mAttachedScrap.add(holder);
            }
        }

        public void detachDirtyScrapViews() {
            final RecycledViewPool pool = getRecycledViewPool();
            final int count = mAttachedScrap.size();
            for (int i = 0; i < count; i++) {
                final ViewHolder holder = mAttachedScrap.get(i);
                if (holder.itemView.getComponentParent() == RecyclerContainer.this && holder.mIsDirty) {
                    removeView(holder.itemView);
                    pool.putRecycledView(holder);
                    dispatchViewRecycled(holder);
                }
            }
            mAttachedScrap.clear();
        }

        ViewHolder getScrapViewForPosition(int position, int type) {
            // Look in our attached views first
            final int count = mAttachedScrap.size();

            for (int i = 0; i < count; i++) {
                final ViewHolder holder = mAttachedScrap.get(i);

                if (holder.getPosition() == position) {
                    // Assumption: if the position record still matches, the type is also correct.
                    mAttachedScrap.remove(i);
                    return holder;
                }
            }
            return getRecycledViewPool().getRecycledView(type);
        }

        ViewHolder getScrapViewForId(long id, int type) {
            // Look in our attached views first
            final int count = mAttachedScrap.size();
            for (int i = 0; i < count; i++) {
                final ViewHolder holder = mAttachedScrap.get(i);
                if (holder.getItemId() == id) {
                    if (type == holder.getItemViewType()) {
                        mAttachedScrap.remove(i);
                        return holder;
                    } else {
                        break;
                    }
                }
            }
            // That didn't work, look for an unordered view of the right type instead.
            // The holder's position won't match so the calling code will need to have
            // the adapter rebind it.
            return getRecycledViewPool().getRecycledView(type);
        }

        void onGenericDataChanged() {
            clear();
        }

        void onAdapterChanged() {
            clear();
            final int typeCount = mAdapter != null ? mAdapter.getItemViewTypeCount() : 0;
            getRecycledViewPool().reset(typeCount);
        }

        void offsetPositionRecordsForInsert(int insertedAt, int count) {
            int cachedCount = this.mCachedViews.size();

            for(int i = 0; i < cachedCount; ++i) {
                RecyclerContainer.ViewHolder holder = (RecyclerContainer.ViewHolder)this.mCachedViews.get(i);
                if (holder != null && holder.mPosition >= insertedAt) {
                    holder.offsetPosition(count, true);
                }
            }
        }

        void offsetPositionRecordsForRemove(int removedFrom, int count, boolean applyToPreLayout) {
            int removedEnd = removedFrom + count;
            int cachedCount = this.mCachedViews.size();

            for(int i = cachedCount - 1; i >= 0; --i) {
                RecyclerContainer.ViewHolder holder = (RecyclerContainer.ViewHolder)this.mCachedViews.get(i);
                if (holder != null) {
                    if (holder.mPosition >= removedEnd) {
                        holder.offsetPosition(-count, applyToPreLayout);
                    } else if (holder.mPosition >= removedFrom) {
                        holder.addFlags(8);
                        this.recycleCachedViewAt(i);
                    }
                }
            }
        }
    }

    /**
     * Base class for an Adapter
     *
     * <p>Adapters provide a binding from an app-specific data set to views that are displayed
     * within a {link RecyclerContainer}.</p>
     */
    public static abstract class Adapter {
        private final AdapterDataObservable mObservable = new AdapterDataObservable();
        private boolean mHasStableIds = false;
        private int mViewTypeCount = 1;

        public abstract ViewHolder createViewHolder(ContainerGroup parent, int viewType);

        public abstract void bindViewHolder(ViewHolder holder, int position);

        /**
         * Return the view type of the item at <code>position</code> for the purposes
         * of view recycling.
         *
         * <p>The default implementation of this method returns 0, making the assumption of
         * the default item view type count of 1. If you change the item view type using
         * {link #setItemViewTypeCount(int)} you should also override this method to return
         * the correct view type for each item in your data set.</p>
         *
         * @param position position to query
         * @return integer in the range [0-{link #getItemViewTypeCount()}) identifying the type
         * of the view needed to represent the item at <code>position</code>
         */
        public int getItemViewType(int position) {
            return 0;
        }

        /**
         * Set the number of item view types required by this adapter to display its data set.
         * This may not be changed while the adapter has observers - e.g. while the adapter
         * is set on a {#link RecyclerContainer}.
         *
         * @param count Number of item view types required
         *              see #getItemViewTypeCount()
         *              see #getItemViewType(int)
         */
        public void setItemViewTypeCount(int count) {
            if (hasObservers()) {
                throw new IllegalStateException("Cannot change the item view type count while " +
                        "the adapter has registered observers.");
            }
            if (count < 1) {
                throw new IllegalArgumentException("Adapter must support at least 1 view type");
            }
            mViewTypeCount = count;
        }

        /**
         * Retrieve the number of item view types required by this adapter to display its data set.
         *
         * @return Number of item view types supported
         * see #setItemViewTypeCount(int)
         * see #getItemViewType(int)
         */
        public final int getItemViewTypeCount() {
            return mViewTypeCount;
        }

        public void setHasStableIds(boolean hasStableIds) {
            if (hasObservers()) {
                throw new IllegalStateException("Cannot change whether this adapter has " +
                        "stable IDs while the adapter has registered observers.");
            }
            mHasStableIds = true;
        }

        /**
         * Return the stable ID for the item at <code>position</code>. If {link #hasStableIds()}
         * would return false this method should return {link #NO_ID}. The default implementation
         * of this method returns {link #NO_ID}.
         *
         * @param position Adapter position to query
         * @return the stable ID of the item at position
         */
        public long getItemId(int position) {
            return NO_ID;
        }

        public abstract int getItemCount();

        /**
         * Returns true if this adapter publishes a unique <code>long</code> value that can
         * act as a key for the item at a given position in the data set. If that item is relocated
         * in the data set, the ID returned for that item should be the same.
         *
         * @return true if this adapter's items have stable IDs
         */
        public final boolean hasStableIds() {
            return this.mHasStableIds;
        }

        /**
         * Called when a view created by this adapter has been recycled.
         *
         * <p>A view is recycled when a {link LayoutManager} decides that it no longer
         * needs to be attached to its parent {link RecyclerContainer}. This can be because it has
         * fallen out of visibility or a set of cached views represented by views still
         * attached to the parent RecyclerContainer. If an item view has large or expensive data
         * bound to it such as large bitmaps, this may be a good place to release those
         * resources.</p>
         *
         * @param holder The ViewHolder for the view being recycled
         */
        public void onViewRecycled(ViewHolder holder) {
        }

        public final boolean hasObservers() {
            return mObservable.hasObservers();
        }

        public void registerAdapterDataObserver(AdapterDataObserver observer) {
            mObservable.registerObserver(observer);
        }

        public void unregisterAdapterDataObserver(AdapterDataObserver observer) {
            mObservable.unregisterObserver(observer);
        }

        public void notifyDataSetChanged() {
            mObservable.notifyChanged();
        }

        public void notifyItemChanged(int position) {
            mObservable.notifyItemRangeChanged(position, 1);
        }

        public void notifyItemRangeChanged(int positionStart, int itemCount) {
            mObservable.notifyItemRangeChanged(positionStart, itemCount);
        }

        public void notifyDataItemInserted(int position) {
            mObservable.notifyItemRangeInserted(position, 1);
        }

        public void notifyDataItemRangeInserted(int positionStart, int itemCount) {
            mObservable.notifyItemRangeInserted(positionStart, itemCount);
        }

        public void notifyDataItemRemoved(int position) {
            mObservable.notifyItemRangeRemoved(position, 1);
        }

        public void notifyDataItemRangeRemoved(int positionStart, int itemCount) {
            mObservable.notifyItemRangeRemoved(positionStart, itemCount);
        }

        public void onViewAttachedToWindow(RecyclerContainer.ViewHolder holder) {
        }

        public void onViewDetachedFromWindow(RecyclerContainer.ViewHolder holder) {
        }

        public boolean onFailedToRecycleView(RecyclerContainer.ViewHolder holder) {
            return false;
        }

        public void onAttachedToRecyclerView(RecyclerContainer recyclerView) {
        }

        public void onDetachedFromRecyclerView(RecyclerContainer recyclerView) {
        }
    }

    /**
     * A <code>LayoutManager</code> is responsible for measuring and positioning item views
     * within a <code>RecyclerContainer</code> as well as determining the policy for when to recycle
     * item views that are no longer visible to the user. By changing the <code>LayoutManager</code>
     * a <code>RecyclerContainer</code> can be used to implement a standard vertically scrolling list,
     * a uniform grid, staggered grids, horizontally scrolling collections and more. Several stock
     * layout managers are provided for general use.
     */
    public static abstract class LayoutManager {
        RecyclerContainer mRecyclerView;
        private int mWidthMode;
        private int mHeightMode;
        private int mWidth;
        private int mHeight;
        boolean mAutoMeasure;
        boolean mRequestedSimpleAnimations;
        boolean mIsAttachedToWindow;
        int mPrefetchMaxCountObserved;
        ChildHelper mChildHelper;
        private boolean mItemPrefetchEnabled;
        private boolean mMeasurementCacheEnabled = true;

        public LayoutManager() {
            this.mRequestedSimpleAnimations = false;
            this.mIsAttachedToWindow = false;
            this.mAutoMeasure = false;
            this.mMeasurementCacheEnabled = true;
            this.mItemPrefetchEnabled = true;
        }

        void dispatchAttachedToWindow(RecyclerContainer view) {
            this.mIsAttachedToWindow = true;
            this.onAttachedToWindow(view);
        }

        void dispatchDetachedFromWindow(RecyclerContainer view, RecyclerContainer.Recycler recycler) {
            this.mIsAttachedToWindow = false;
            this.onDetachedFromWindow(view, recycler);
        }

        public void onAttachedToWindow(RecyclerContainer view) {
        }

        public void onDetachedFromWindow(RecyclerContainer view, RecyclerContainer.Recycler recycler) {
            this.onDetachedFromWindow(view);
        }

        /** @deprecated */
        @Deprecated
        public void onDetachedFromWindow(RecyclerContainer view) {
        }

        public boolean isAttachedToWindow() {
            return this.mIsAttachedToWindow;
        }

        public final RecyclerContainer getRecyclerView() {
            return mRecyclerView;
        }

        public void onAttachedToWindow() {
        }

        public void onDetachedFromWindow() {
        }

        public abstract void layoutChildren(Adapter adapter, Recycler recycler);

        /**
         * Create a default <code>LayoutParams</code> object for a child of the RecyclerContainer.
         *
         * <p>LayoutStrategies will often want to use a custom <code>LayoutParams</code> type
         * to store extra information specific to the layout. Client code should subclass
         * {link LayoutParams} for this purpose.</p>
         *
         * @return A new LayoutParams for a child view
         */
        public abstract LayoutParams generateDefaultLayoutParams();

        /**
         * Determines the validity of the supplied LayoutParams object.
         *
         * <p>This should check to make sure that the object is of the correct type
         * and all values are within acceptable ranges.</p>
         *
         * @param lp LayoutParams object to check
         * @return true if this LayoutParams object is valid, false otherwise
         */
        public boolean checkLayoutParams(LayoutParams lp) {
            return lp instanceof LayoutParams;
        }

        /**
         * Create a LayoutParams object suitable for this layout strategy, copying relevant
         * values from the supplied LayoutParams object if possible.
         *
         * @param lp Source LayoutParams object to copy values from
         * @return a new LayoutParams object
         */
        public LayoutParams generateLayoutParams(ComponentContainer.LayoutConfig lp) {
            if (lp instanceof LayoutParams) {
                return new LayoutParams((LayoutParams) lp);
            } else if (lp instanceof LayoutParams) {
                return new LayoutParams((LayoutParams) lp);
            } else {
                return new LayoutParams(lp);
            }
        }

        public void layoutDecorated(Component child, int left, int top, int right, int bottom) {
            final Rect insets = ((LayoutParams) child.getLayoutConfig()).mDecorInsets;
            child.setComponentPosition(left + insets.left, top + insets.top, right - insets.right,
                    bottom - insets.bottom);
        }

        /**
         * Create a LayoutParams object suitable for this layout strategy from
         * an inflated layout resource.
         *
         * @param c     Context for obtaining styled attributes
         * @param attrs AttributeSet describing the supplied arguments
         * @return a new LayoutParams object
         */
        public LayoutParams generateLayoutParams(Context c, AttrSet attrs) {
            return new LayoutParams(c, attrs);
        }

        /**
         * Scroll horizontally by dx pixels in screen coordinates and return the distance traveled.
         * The default implementation does nothing and returns 0.
         *
         * @param dx       distance to scroll by in pixels. X increases as scroll position
         *                 approaches the right.
         * @param adapter  RecyclerContainer.Adapter
         * @param recycler Recycler
         * @return The actual distance scrolled. The return value will be negative if dx was
         * negative and scrolling proceeeded in that direction.
         * <code>Math.abs(result)</code> may be less than dx if a boundary was reached.
         */
        public int scrollHorizontallyBy(int dx, RecyclerContainer.Adapter adapter, Recycler recycler) {
            return 0;
        }

        /**
         * Scroll vertically by dy pixels in screen coordinates and return the distance traveled.
         * The default implementation does nothing and returns 0.
         *
         * @param dy       distance to scroll in pixels. Y increases as scroll position
         *                 approaches the bottom.
         * @param adapter  RecyclerContainer.Adapter
         * @param recycler Recycler
         * @return The actual distance scrolled. The return value will be negative if dy was
         * negative and scrolling proceeeded in that direction.
         * <code>Math.abs(result)</code> may be less than dy if a boundary was reached.
         */
        public int scrollVerticallyBy(int dy, RecyclerContainer.Adapter adapter, Recycler recycler) {
            return 0;
        }

        /**
         * Query if horizontal scrolling is currently supported. The default implementation
         * returns false.
         *
         * @return True if this LayoutManager can scroll the current contents horizontally
         */
        public boolean canScrollHorizontally() {
            return false;
        }

        /**
         * Query if vertical scrolling is currently supported. The default implementation
         * returns false.
         *
         * @return True if this LayoutManager can scroll the current contents vertically
         */
        public boolean canScrollVertically() {
            return false;
        }

        /**
         * Add a view to the currently attached RecyclerContainer if needed. If the view has already
         * been added this method does nothing. LayoutManagers should use this method to add
         * views pulled from a {link Recycler}, as recycled views may already be attached.
         *
         * @param child
         * @param index
         */
        public void addView(Component child, int index) {
            this.addViewInt(child, index, false);
        }

        public void addView(Component child) {
            this.addView(child, -1);
        }

        private void addViewInt(Component child, int index, boolean disappearing) {
            RecyclerContainer.ViewHolder holder = RecyclerContainer.getChildViewHolderInt(child);
            RecyclerContainer.LayoutParams lp = (RecyclerContainer.LayoutParams)child.getLayoutConfig();
            if (!holder.wasReturnedFromScrap() && !holder.isScrap()) {
                if (child.getComponentParent() == this.mRecyclerView) {
                    int currentIndex = this.mChildHelper.indexOfChild(child);
                    if (index == -1) {
                        index = this.mChildHelper.getChildCount();
                    }

                    if (currentIndex == -1) {
                        throw new IllegalStateException("Added View has RecyclerView as parent but view is not a real child. Unfiltered index:" +
                                this.mRecyclerView.getChildIndex(child) + this.mRecyclerView.exceptionLabel());
                    }

                    if (currentIndex != index) {
                        this.mRecyclerView.mLayout.moveView(currentIndex, index);
                    }
                } else {
                    this.mChildHelper.addView(child, index, false);
                    lp.mInsetsDirty = true;
                }
            } else {
                if (holder.isScrap()) {
                    holder.unScrap();
                } else {
                    holder.clearReturnedFromScrapFlag();
                }

                this.mChildHelper.attachViewToParent(child, index, child.getLayoutConfig(), false);
            }
        }

        public void moveView(int fromIndex, int toIndex) {
            Component view = this.getChildAt(fromIndex);
            if (view == null) {
                throw new IllegalArgumentException("Cannot move a child from non-existing index:" + fromIndex + this.mRecyclerView.toString());
            } else {
                this.detachViewAt(fromIndex);
                this.attachView(view, toIndex);
            }
        }

        public void attachView(Component child, int index) {
            this.attachView(child, index, (RecyclerContainer.LayoutParams)child.getLayoutConfig());
        }

        public void attachView(Component child, int index, RecyclerContainer.LayoutParams lp) {
            RecyclerContainer.ViewHolder vh = RecyclerContainer.getChildViewHolderInt(child);
            this.mChildHelper.attachViewToParent(child, index, lp, vh.isRemoved());
        }

        public void removeView(Component child) {
            this.mChildHelper.removeView(child);
        }

        public void removeViewAt(int index) {
            Component child = this.getChildAt(index);
            if (child != null) {
                this.mChildHelper.removeViewAt(index);
            }
        }

        public void removeAllViews() {
            int childCount = this.getChildCount();

            for(int i = childCount - 1; i >= 0; --i) {
                this.mChildHelper.removeViewAt(i);
            }
        }

        public Component findContainingItemView(Component view) {
            if (this.mRecyclerView == null) {
                return null;
            } else {
                Component found = this.mRecyclerView.findContainingItemView(view);
                if (found == null) {
                    return null;
                } else {
                    return this.mChildHelper.isHidden(found) ? null : found;
                }
            }
        }

        public Component findViewByPosition(int position) {
            int childCount = this.getChildCount();

            for(int i = 0; i < childCount; ++i) {
                Component child = this.getChildAt(i);
                RecyclerContainer.ViewHolder vh = RecyclerContainer.getChildViewHolderInt(child);
                if (vh != null && vh.getLayoutPosition() == position && !vh.shouldIgnore() || !vh.isRemoved()) {
                    return child;
                }
            }
            return null;
        }

        public void detachAndScrapView(Component child, RecyclerContainer.Recycler recycler) {
            int index = this.mChildHelper.indexOfChild(child);
            this.scrapOrRecycleView(recycler, index, child);
        }

        public void detachAndScrapViewAt(int index, RecyclerContainer.Recycler recycler) {
            Component child = this.getChildAt(index);
            this.scrapOrRecycleView(recycler, index, child);
        }

        /**
         * c
         * Called when searching for a focusable view in the given direction has failed
         * for the current content of the RecyclerContainer.
         *
         * <p>This is the LayoutManager's opportunity to populate views in the given direction
         * to fulfill the request if it can. The LayoutManager should attach and return
         * the view to be focused.</p>
         *
         * @param focused   The currently focused view
         * @param direction One of {link View#FOCUS_UP}, {link View#FOCUS_DOWN},
         *                  {link View#FOCUS_LEFT}, {link View#FOCUS_RIGHT},
         *                  {link View#FOCUS_BACKWARD}, {link View#FOCUS_FORWARD}
         *                  or 0 for not applicable
         * @param recycler  The recycler to use for obtaining views for currently offscreen items
         * @return The chosen view to be focused
         */
        public Component onFocusSearchFailed(Component focused, int direction, Recycler recycler) {
            return null;
        }

        /**
         * Called when a child of the RecyclerContainer wants a particular rectangle to be positioned
         * onto the screen.
         *
         * <p>The base implementation will attempt to perform a standard programmatic scroll
         * to bring the given rect into view, within the padded area of the RecyclerContainer.</p>
         *
         * @param child     The direct child making the request.
         * @param rect      The rectangle in the child's coordinates the child
         *                  wishes to be on the screen.
         * @param immediate True to forbid animated or delayed scrolling,
         *                  false otherwise
         * @return Whether the group scrolled to handle the operation
         */
        public boolean requestChildRectangleOnScreen(Component child, Rect rect, boolean immediate) {
            final int parentLeft = mRecyclerView.getPaddingLeft();
            final int parentTop = mRecyclerView.getPaddingTop();
            final int parentRight = mRecyclerView.getWidth() - mRecyclerView.getPaddingRight();
            final int parentBottom = mRecyclerView.getHeight() - mRecyclerView.getPaddingBottom();
            final int childLeft = child.getLeft() + rect.left;
            final int childTop = child.getTop() + rect.top;
            final int childRight = childLeft + rect.right;
            final int childBottom = childTop + rect.bottom;
            final int offScreenLeft = Math.min(0, childLeft - parentLeft);
            final int offScreenTop = Math.min(0, childTop - parentTop);
            final int offScreenRight = Math.max(0, childRight - parentRight);
            final int offScreenBottom = Math.max(0, childBottom - parentBottom);
            // Favor the "start" layout direction over the end when bringing one side or the other
            // of a large rect into view.
            final int dx;
            if (HarmonyConstant.getLayoutDirection(mRecyclerView) == HarmonyConstant.LAYOUT_DIRECTION_RTL) {
                dx = offScreenRight != 0 ? offScreenRight : offScreenLeft;
            } else {
                dx = offScreenLeft != 0 ? offScreenLeft : offScreenRight;
            }
            // Favor bringing the top into view over the bottom
            final int dy = offScreenTop != 0 ? offScreenTop : offScreenBottom;
            if (dx != 0 || dy != 0) {
                if (immediate) {
                    mRecyclerView.scrollBy(dx, dy);
                } else {
                    mRecyclerView.smoothScrollBy(dx, dy);
                }
                return true;
            }
            return false;
        }

        /**
         * Called when a descendant view of the RecyclerContainer requests focus.
         *
         * <p>If a LayoutManager wishes to override the default behavior of simply requesting
         * the descendant's visible area on screen it may override this method and do so.
         * A LayoutManager wishing to keep focused views aligned in a specific portion of the
         * view may implement that behavior here.</p>
         *
         * <p>If the LayoutManager executes different behavior that should override the default
         * behavior of scrolling the focused child on screen instead of running alongside it,
         * this method should return true.</p>
         *
         * @param child   Direct child of the RecyclerContainer containing the newly focused view
         * @param focused The newly focused view. This may be the same view as child
         * @return true if the default scroll behavior should be suppressed
         */
        public boolean onRequestChildFocus(Component child, Component focused) {
            return false;
        }

        /**
         * Returns the measured width of the given child, plus the additional size of
         * any insets applied by {@link ItemDecoration ItemDecorations}.
         *
         * @param child Child view to query
         * @return child's measured width plus <code>ItemDecoration</code> insets
         * <p>
         * see View#getMeasuredWidth()
         */
        public int getDecoratedMeasuredWidth(Component child) {
            final Rect insets = ((LayoutParams) child.getLayoutConfig()).mDecorInsets;
            return child.getWidth()/*getMeasuredWidth()*/ + insets.left + insets.right;
        }

        /**
         * Returns the measured height of the given child, plus the additional size of
         * any insets applied by {@link ItemDecoration ItemDecorations}.
         *
         * @param child Child view to query
         * @return child's measured height plus <code>ItemDecoration</code> insets
         * <p>
         * see View#getMeasuredHeight()
         */
        public int getDecoratedMeasuredHeight(Component child) {
            final Rect insets = ((LayoutParams) child.getLayoutConfig()).mDecorInsets;
            return child.getHeight()/*getMeasuredHeight()*/ + insets.top + insets.bottom;
        }

        /**
         * Returns the left edge of the given child view within its parent, offset by any applied
         * {@link ItemDecoration ItemDecorations}.
         *
         * @param child Child to query
         * @return Child left edge with offsets applied
         * see #getLeftDecorationWidth(View)
         */
        public int getDecoratedLeft(Component child) {
            return child.getLeft() - getLeftDecorationWidth(child);
        }

        /**
         * Returns the top edge of the given child view within its parent, offset by any applied
         * {@link ItemDecoration ItemDecorations}.
         *
         * @param child Child to query
         * @return Child top edge with offsets applied
         * see #getTopDecorationHeight(View)
         */
        public int getDecoratedTop(Component child) {
            return child.getTop() - getTopDecorationHeight(child);
        }

        /**
         * Returns the right edge of the given child view within its parent, offset by any applied
         * {@link ItemDecoration ItemDecorations}.
         *
         * @param child Child to query
         * @return Child right edge with offsets applied
         * @ee #getRightDecorationWidth(View)
         */
        public int getDecoratedRight(Component child) {
            return child.getRight() + getRightDecorationWidth(child);
        }

        /**
         * Returns the bottom edge of the given child view within its parent, offset by any applied
         * {@link ItemDecoration ItemDecorations}.
         *
         * @param child Child to query
         * @return Child bottom edge with offsets applied
         * see #getBottomDecorationHeight(View)
         */
        public int getDecoratedBottom(Component child) {
            return child.getBottom() + getBottomDecorationHeight(child);
        }

        /**
         * Returns the total height of item decorations applied to child's top.
         * <p>
         * Note that this value is not updated until the View is measured or
         * {link #calculateItemDecorationsForChild(View, Rect)} is called.
         *
         * @param child Child to query
         * @return The total height of item decorations applied to the child's top.
         * see #getDecoratedTop(View)
         * see #calculateItemDecorationsForChild(View, Rect)
         */
        public int getTopDecorationHeight(Component child) {
            return ((LayoutParams) child.getLayoutConfig()).mDecorInsets.top;
        }

        /**
         * Returns the total height of item decorations applied to child's bottom.
         * <p>
         * Note that this value is not updated until the View is measured or
         * {link #calculateItemDecorationsForChild(View, Rect)} is called.
         *
         * @param child Child to query
         * @return The total height of item decorations applied to the child's bottom.
         * see #getDecoratedBottom(View)
         * see #calculateItemDecorationsForChild(View, Rect)
         */
        public int getBottomDecorationHeight(Component child) {
            return ((LayoutParams) child.getLayoutConfig()).mDecorInsets.bottom;
        }

        /**
         * Returns the total width of item decorations applied to child's left.
         * <p>
         * Note that this value is not updated until the View is measured or
         * {link #calculateItemDecorationsForChild(View, Rect)} is called.
         *
         * @param child Child to query
         * @return The total width of item decorations applied to the child's left.
         * see #getDecoratedLeft(View)
         * see #calculateItemDecorationsForChild(View, Rect)
         */
        public int getLeftDecorationWidth(Component child) {
            return ((LayoutParams) child.getLayoutConfig()).mDecorInsets.left;
        }

        /**
         * Returns the total width of item decorations applied to child's right.
         * <p>
         * Note that this value is not updated until the View is measured or
         * {link #calculateItemDecorationsForChild(View, Rect)} is called.
         *
         * @param child Child to query
         * @return The total width of item decorations applied to the child's right.
         * see #getDecoratedRight(View)
         * see #calculateItemDecorationsForChild(View, Rect)
         */
        public int getRightDecorationWidth(Component child) {
            return ((LayoutParams) child.getLayoutConfig()).mDecorInsets.right;
        }

        public int getWidthMode() {
            return this.mWidthMode;
        }

        public int getHeightMode() {
            return this.mHeightMode;
        }

        public int getWidth() {
            return this.mWidth;
        }

        public int getHeight() {
            return this.mHeight;
        }

        public int getPaddingLeft() {
            return this.mRecyclerView != null ? this.mRecyclerView.getPaddingLeft() : 0;
        }

        public int getPaddingTop() {
            return this.mRecyclerView != null ? this.mRecyclerView.getPaddingTop() : 0;
        }

        public int getPaddingRight() {
            return this.mRecyclerView != null ? this.mRecyclerView.getPaddingRight() : 0;
        }

        public int getPaddingBottom() {
            return this.mRecyclerView != null ? this.mRecyclerView.getPaddingBottom() : 0;
        }

        public boolean isFocused() {
            return this.mRecyclerView != null && this.mRecyclerView.isFocused();
        }

        public boolean hasFocus() {
            return this.mRecyclerView != null && this.mRecyclerView.hasFocus();
        }

        void setRecyclerView(RecyclerContainer recyclerView) {
            if (recyclerView == null) {
                this.mRecyclerView = null;
                this.mChildHelper = null;
                this.mWidth = 0;
                this.mHeight = 0;
            } else {
                this.mRecyclerView = recyclerView;
                this.mChildHelper = recyclerView.mChildHelper;
                this.mWidth = recyclerView.getWidth();
                this.mHeight = recyclerView.getHeight();
            }

            this.mWidthMode = 1073741824;
            this.mHeightMode = 1073741824;
        }

        public void measureChildWithMargins(Component child, int widthUsed, int heightUsed) {
            RecyclerContainer.LayoutParams lp = (RecyclerContainer.LayoutParams)child.getLayoutConfig();
            Rect insets = this.mRecyclerView.getItemDecorInsetsForChild(child);
            widthUsed += insets.left + insets.right;
            heightUsed += insets.top + insets.bottom;
            int widthSpec = getChildMeasureSpec(this.getWidth(), this.getWidthMode(), this.getPaddingLeft() + this.getPaddingRight()
                    + lp.getMarginLeft() + lp.getMarginRight() + widthUsed, lp.width, this.canScrollHorizontally());

            int heightSpec = getChildMeasureSpec(this.getHeight(), this.getHeightMode(), this.getPaddingTop() + this.getPaddingBottom()
                    + lp.getMarginTop() + lp.getMarginBottom() + heightUsed, lp.height, this.canScrollVertically());

            if (this.shouldMeasureChild(child, widthSpec, heightSpec, lp)) {
                child.estimateSize(widthSpec, heightSpec);
            }
        }

        boolean shouldMeasureChild(Component child, int widthSpec, int heightSpec, RecyclerContainer.LayoutParams lp) {
            return  !this.mMeasurementCacheEnabled || !isMeasurementUpToDate(child.getWidth(), widthSpec, lp.width) ||
                    !isMeasurementUpToDate(child.getHeight(), heightSpec, lp.height);
        }

        private static boolean isMeasurementUpToDate(int childSize, int spec, int dimension) {
            int specMode = EstimateSpec.getMode(spec);
            int specSize = EstimateSpec.getSize(spec);
            if (dimension > 0 && childSize != dimension) {
                return false;
            } else {
                switch(specMode) {
                    case -2147483648:
                        return specSize >= childSize;
                    case 0:
                        return true;
                    case 1073741824:
                        return specSize == childSize;
                    default:
                        return false;
                }
            }
        }

        public static int getChildMeasureSpec(int parentSize, int parentMode, int padding, int childDimension, boolean canScroll) {
            int size = Math.max(0, parentSize - padding);
            int resultSize = 0;
            int resultMode = 0;
            if (canScroll) {
                if (childDimension >= 0) {
                    resultSize = childDimension;
                    resultMode = 1073741824;
                } else if (childDimension == -1) {
                    switch(parentMode) {
                        case -2147483648:
                        case 1073741824:
                            resultSize = size;
                            resultMode = parentMode;
                            break;
                        case 0:
                            resultSize = 0;
                            resultMode = 0;
                    }
                } else if (childDimension == -2) {
                    resultSize = 0;
                    resultMode = 0;
                }
            } else if (childDimension >= 0) {
                resultSize = childDimension;
                resultMode = 1073741824;
            } else if (childDimension == -1) {
                resultSize = size;
                resultMode = parentMode;
            } else if (childDimension == -2) {
                resultSize = size;
                if (parentMode != -2147483648 && parentMode != 1073741824) {
                    resultMode = 0;
                } else {
                    resultMode = -2147483648;
                }
            }
            return EstimateSpec.getSizeWithMode(resultSize, resultMode);
        }

        public void detachAndScrapAttachedViews(RecyclerContainer.Recycler recycler) {
            int childCount = this.mRecyclerView.getChildCount();

            for(int i = childCount - 1; i >= 0; --i) {
                Component v = this.mRecyclerView.getChildAt(i);
                this.scrapOrRecycleView(recycler, i, v);
            }
        }

        private void scrapOrRecycleView(RecyclerContainer.Recycler recycler, int index, Component view) {
            RecyclerContainer.ViewHolder viewHolder = RecyclerContainer.getChildViewHolderInt(view);

            if (!viewHolder.shouldIgnore()) {
                if (viewHolder.isInvalid() && !viewHolder.isRemoved() && !this.mRecyclerView.mAdapter.hasStableIds()) {
                    this.removeViewAt(index);
                    recycler.recycleViewHolderInternal(viewHolder);
                } else {
                    this.detachViewAt(index);
                    recycler.scrapView(view);
                    this.mRecyclerView.removeView(viewHolder.itemView);
                }
            }
        }

        public void scrollToPosition(int position) {
        }

        public void smoothScrollToPosition(RecyclerContainer recyclerView, RecyclerContainer.Adapter adapter, int position) {
            LogUtil.error("RecyclerView", "You must override smoothScrollToPosition to support smooth scrolling");
        }

        public final boolean isItemPrefetchEnabled() {
            return this.mItemPrefetchEnabled;
        }

        void setExactMeasureSpecsFrom(RecyclerContainer recyclerView) {
            this.setMeasureSpecs(EstimateSpec.getSizeWithMode(recyclerView.getWidth(), 1073741824), EstimateSpec.getSizeWithMode(recyclerView.getHeight(), 1073741824));
        }

        void setMeasureSpecs(int wSpec, int hSpec) {
            this.mWidth = EstimateSpec.getSize(wSpec);
            this.mWidthMode = EstimateSpec.getMode(wSpec);
            if (this.mWidthMode == 0) {
                this.mWidth = 0;
            }

            this.mHeight = EstimateSpec.getSize(hSpec);
            this.mHeightMode = EstimateSpec.getMode(hSpec);
            if (this.mHeightMode == 0) {
                this.mHeight = 0;
            }
        }

        public void removeAndRecycleView(Component child, RecyclerContainer.Recycler recycler) {
            this.removeView(child);
            recycler.recycleView(child);
        }

        public void removeAndRecycleViewAt(int index, RecyclerContainer.Recycler recycler) {
            Component view = this.getChildAt(index);
            this.removeViewAt(index);
            recycler.recycleView(view);
        }

        public int getChildCount() {
            return this.mChildHelper != null ? this.mChildHelper.getChildCount() : 0;
        }

        public void detachView(Component child) {
            int ind = this.mChildHelper.indexOfChild(child);
            if (ind >= 0) {
                this.detachViewInternal(ind, child);
            }
        }

        public void detachViewAt(int index) {
            this.detachViewInternal(index, this.getChildAt(index));
        }

        public Component getChildAt(int index) {
            return this.mChildHelper != null ? this.mChildHelper.getChildAt(index) : null;
        }

        private void detachViewInternal(int index, Component view) {
            this.mChildHelper.detachViewFromParent(index);
        }

        public void removeAndRecycleAllViews(RecyclerContainer.Recycler recycler) {
            for(int i = this.getChildCount() - 1; i >= 0; --i) {
                Component view = this.getChildAt(i);
                RecyclerContainer.ViewHolder vh = RecyclerContainer.getChildViewHolderInt(view);
                if (!RecyclerContainer.getChildViewHolderInt(view).shouldIgnore()) {
                    this.removeAndRecycleViewAt(i, recycler);
                }
            }
        }

        void removeAndRecycleScrapInt(RecyclerContainer.Recycler recycler) {
            int scrapCount = recycler.getScrapCount();

            for(int i = scrapCount - 1; i >= 0; --i) {
                Component scrap = recycler.getScrapViewAt(i);
                RecyclerContainer.ViewHolder vh = RecyclerContainer.getChildViewHolderInt(scrap);
                if (!vh.shouldIgnore()) {
                    vh.setIsRecyclable(false);
                    if (vh.isTmpDetached()) {
                        this.mRecyclerView.removeDetachedView(scrap, false);
                    }

                    if (this.mRecyclerView.mItemAnimator != null) {
                        this.mRecyclerView.mItemAnimator.endAnimation(vh);
                    }
                    vh.setIsRecyclable(true);
                    recycler.quickRecycleScrapView(scrap);
                }
            }
            recycler.clearScrap();
            if (scrapCount > 0) {
                this.mRecyclerView.invalidate();
            }
        }

        public final void setItemPrefetchEnabled(boolean enabled) {
            if (enabled != this.mItemPrefetchEnabled) {
                this.mItemPrefetchEnabled = enabled;
                this.mPrefetchMaxCountObserved = 0;
                if (this.mRecyclerView != null) {
                    this.mRecyclerView.mRecycler.updateViewCacheSize();
                }
            }
        }

        public void onAdapterChanged(RecyclerContainer.Adapter oldAdapter, RecyclerContainer.Adapter newAdapter) {
        }

        public void onMeasure(RecyclerContainer.Recycler recycler, RecyclerContainer.Adapter adapter, int widthSpec, int heightSpec) {
            this.mRecyclerView.defaultOnMeasure(widthSpec, heightSpec);
        }

        public static int chooseSize(int spec, int desired, int min) {
            int mode = EstimateSpec.getMode(spec);
            int size = EstimateSpec.getSize(spec);
            switch(mode) {
                case -2147483648:
                    return Math.min(size, Math.max(desired, min));
                case 0:
                default:
                    return Math.max(desired, min);
                case 1073741824:
                    return size;
            }
        }

        @Deprecated
        public void setAutoMeasureEnabled(boolean enabled) {
            this.mAutoMeasure = enabled;
        }

        public boolean isAutoMeasureEnabled() {
            return this.mAutoMeasure;
        }

        public void onScrollStateChanged(int state) {
        }
    }

    /**
     * An ItemDecoration allows the application to add a special drawing and layout offset
     * to specific item views from the adapter's data set. This can be useful for drawing dividers
     * between items, highlights, visual grouping boundaries and more.
     *
     * <p>All ItemDecorations are drawn in the order they were added,
     * below the item views themselves.</p>
     */
    public interface ItemDecoration {
        /**
         * Draw any appropriate decorations into the Canvas supplied to the RecyclerContainer.
         * Any content drawn by this method will appear below item views.
         *
         * @param c Canvas to draw into
         */
        public void onDraw(Canvas c);

        /**
         * Draw any appropriate decorations into the Canvas supplied to the RecyclerContainer.
         * Any content drawn by this method will appear above item views.
         *
         * @param c Canvas to draw into
         */
        public void onDrawOver(Canvas c);

        /**
         * Retrieve any offsets for the given item. Each field of <code>outRect</code> specifies
         * the number of pixels that the item view should be inset by, similar to padding or margin.
         *
         * <p>If this ItemDecoration does not affect the positioning of item views it should set
         * all four fields of <code>outRect</code> (left, top, right, bottom) to zero
         * before returning.</p>
         *
         * @param outRect      Rect to receive the output.
         * @param itemPosition Adapter position of the item to offset
         */
        public void getItemOffsets(Rect outRect, int itemPosition);
    }

    /**
     * An ItemTouchListener allows the application to intercept touch events in progress at the
     * view hierarchy level of the RecyclerContainer, before those touch events are considered for
     * RecyclerContainer's own scrolling behavior.
     *
     * <p>This can be useful for applications that wish to implement various forms of gestural
     * manipulation of item views within the RecyclerContainer. ItemTouchListeners may intercept
     * a touch interaction already in progress even if the RecyclerContainer is already handling that
     * gesture stream itself for the purposes of scrolling.</p>
     */
    public interface ItemTouchListener {
        /**
         * Silently observe and/or take over touch events sent to the RecyclerContainer
         * before they are handled by either the RecyclerContainer itself or its child views.
         *
         * <p>The onInterceptTouchEvent methods of each attached ItemTouchListener will be run
         * in the order in which each listener was added, before any other touch processing
         * by the RecyclerContainer itself or child views occurs.</p>
         *
         * @param e MotionEvent describing the touch event. All coordinates are in
         *          the RecyclerContainer's coordinate system.
         * @return true if this ItemTouchListener wishes to begin intercepting touch events, false
         * to continue with the current behavior and continue observing future events in
         * the gesture.
         */
        public boolean onInterceptTouchEvent(MotionEvent e);

        /**
         * Process a touch event as part of a gesture that was claimed by returning true from
         * a previous call to {link #onInterceptTouchEvent}.
         *
         * @param e MotionEvent describing the touch event. All coordinates are in
         *          the RecyclerContainer's coordinate system.
         */
        public void onTouchEvent(MotionEvent e);
    }

    public interface OnScrollListener {
        public void onScrollStateChanged(int newState);

        public void onScrolled(int dx, int dy);
    }

    public interface RecyclerListener {
        public void onViewRecycled(ViewHolder holder);
    }

    /**
     * A ViewHolder describes an item view and metadata about its place within the RecyclerContainer.
     *
     * <p>{link Adapter} implementations should subclass ViewHolder and add fields for caching
     * potentially expensive {link View#findViewById(int)} results.</p>
     *
     * <p>While {link LayoutParams} belong to the
     * {link ViewHolder ViewHolders} belong to the adapter. Adapters should feel free to use
     * their own custom ViewHolder implementations to store data that makes binding view contents
     * easier. Implementations should assume that individual item views will hold strong references
     * to <code>ViewHolder</code> objects and that <code>RecyclerContainer</code> instances may hold
     * strong references to extra off-screen item views for caching purposes</p>
     */
    public static abstract class ViewHolder {
        public final Component itemView;
        int mPosition = NO_POSITION;
        long mItemId = NO_ID;
        int mItemViewType = INVALID_TYPE;
        boolean mIsDirty = true;
        RecyclerContainer.Recycler mScrapContainer = null;
        RecyclerContainer.ViewHolder mShadowingHolder = null;
        boolean mInChangeScrap = false;
        RecyclerContainer mOwnerRecyclerView;
        List<Object> mPayloads = null;
        List<Object> mUnmodifiedPayloads = null;
        RecyclerContainer.ViewHolder mShadowedHolder = null;
        private static final List<Object> FULLUPDATE_PAYLOADS = Collections.emptyList();
        private int mIsRecyclableCount = 0;
        int mFlags;
        int mOldPosition = -1;
        int mPreLayoutPosition = -1;

        public ViewHolder(Component itemView) {
            if (itemView == null) {
                throw new IllegalArgumentException("itemView may not be null");
            }
            this.itemView = itemView;
        }

        /** @deprecated */
        @Deprecated
        public final int getPosition() {
            return this.mPreLayoutPosition == -1 ? this.mPosition : this.mPreLayoutPosition;
        }

        public final int getLayoutPosition() {
            return this.mPreLayoutPosition == -1 ? this.mPosition : this.mPreLayoutPosition;
        }

        public final long getItemId() {
            return mItemId;
        }

        public final int getItemViewType() {
            return mItemViewType;
        }

        boolean isScrap() {
            return this.mScrapContainer != null;
        }

        void unScrap() {
            this.mScrapContainer.unscrapView(this);
        }

        void setScrapContainer(RecyclerContainer.Recycler recycler, boolean isChangeScrap) {
            this.mScrapContainer = recycler;
            this.mInChangeScrap = isChangeScrap;
        }

        void clearReturnedFromScrapFlag() {
            this.mFlags &= -33;
        }

        boolean shouldIgnore() {
            return (this.mFlags & 128) != 0;
        }

        void clearTmpDetachFlag() {
            this.mFlags &= -257;
        }

        void stopIgnoring() {
            this.mFlags &= -129;
        }

        boolean isInvalid() {
            return (this.mFlags & 4) != 0;
        }

        boolean needsUpdate() {
            return (this.mFlags & 2) != 0;
        }

        boolean isBound() {
            return (this.mFlags & 1) != 0;
        }

        boolean isRemoved() {
            return (this.mFlags & 8) != 0;
        }

        boolean hasAnyOfTheFlags(int flags) {
            return (this.mFlags & flags) != 0;
        }

        boolean isTmpDetached() {
            return (this.mFlags & 256) != 0;
        }

        boolean isAdapterPositionUnknown() {
            return (this.mFlags & 512) != 0 || this.isInvalid();
        }

        void setFlags(int flags, int mask) {
            this.mFlags = this.mFlags & ~mask | flags & mask;
        }

        void addFlags(int flags) {
            this.mFlags |= flags;
        }

        void clearPayload() {
            if (this.mPayloads != null) {
                this.mPayloads.clear();
            }
            this.mFlags &= -1025;
        }

        void resetInternal() {
            this.mFlags = 0;
            this.mIsDirty = true;
            this.mPosition = -1;
            this.mItemId = -1L;
            this.mIsRecyclableCount = 0;
            this.mShadowedHolder = null;
            this.mShadowingHolder = null;
            this.mPreLayoutPosition = -1;
            this.mOldPosition = -1;
            this.clearPayload();
        }

        boolean isUpdated() {
            return (this.mFlags & 2) != 0;
        }

        void clearOldPosition() {
            this.mOldPosition = -1;
            this.mPreLayoutPosition = -1;
        }

        void saveOldPosition() {
            if (this.mOldPosition == -1) {
                this.mOldPosition = this.mPosition;
            }
        }

        public final int getOldPosition() {
            return this.mOldPosition;
        }

        void addChangePayload(Object payload) {
            if (payload == null) {
                this.addFlags(1024);
            } else if ((this.mFlags & 1024) == 0) {
                this.createPayloadsIfNeeded();
                this.mPayloads.add(payload);
            }
        }

        private void createPayloadsIfNeeded() {
            if (this.mPayloads == null) {
                this.mPayloads = new ArrayList();
                this.mUnmodifiedPayloads = Collections.unmodifiableList(this.mPayloads);
            }
        }

        List<Object> getUnmodifiedPayloads() {
            if ((this.mFlags & 1024) == 0) {
                return this.mPayloads != null && this.mPayloads.size() != 0 ? this.mUnmodifiedPayloads : FULLUPDATE_PAYLOADS;
            } else {
                return FULLUPDATE_PAYLOADS;
            }
        }

        public final void setIsRecyclable(boolean recyclable) {
            this.mIsRecyclableCount = recyclable ? this.mIsRecyclableCount - 1 : this.mIsRecyclableCount + 1;
            if (this.mIsRecyclableCount < 0) {
                this.mIsRecyclableCount = 0;
                LogUtil.error("View", "isRecyclable decremented below 0: unmatched pair of setIsRecyable() calls for " + this);
            } else if (!recyclable && this.mIsRecyclableCount == 1) {
                this.mFlags |= 16;
            } else if (recyclable && this.mIsRecyclableCount == 0) {
                this.mFlags &= -17;
            }
        }

        boolean shouldBeKeptAsChild() {
            return (this.mFlags & 16) != 0;
        }

        boolean doesTransientStatePreventRecycling() {
            return (this.mFlags & 16) == 0;
        }

        public final boolean isRecyclable() {
            return (this.mFlags & 16) == 0;
        }

        boolean wasReturnedFromScrap() {
            return (this.mFlags & 32) != 0;
        }

        void flagRemovedAndOffsetPosition(int mNewPosition, int offset, boolean applyToPreLayout) {
            this.addFlags(8);
            this.offsetPosition(offset, applyToPreLayout);
            this.mPosition = mNewPosition;
        }

        void offsetPosition(int offset, boolean applyToPreLayout) {
            if (this.mOldPosition == -1) {
                this.mOldPosition = this.mPosition;
            }

            if (this.mPreLayoutPosition == -1) {
                this.mPreLayoutPosition = this.mPosition;
            }

            if (applyToPreLayout) {
                this.mPreLayoutPosition += offset;
            }

            this.mPosition += offset;
            if (this.itemView.getLayoutConfig() != null) {
                ((RecyclerContainer.LayoutParams)this.itemView.getLayoutConfig()).mInsetsDirty = true;
            }
        }
    }

    /**
     * Queued operation to happen when child views are update.
     */
    private static class UpdateOp {
        public static final int ADD = 0;
        public static final int REMOVE = 1;
        public static final int UPDATE = 2;
        static final int POOL_SIZE = 30;
        public int cmd;
        public int positionStart;
        public int itemCount;

        public UpdateOp(int cmd, int positionStart, int itemCount) {
            this.cmd = cmd;
            this.positionStart = positionStart;
            this.itemCount = itemCount;
        }
    }

    UpdateOp obtainUpdateOp(int cmd, int positionStart, int itemCount) {
        UpdateOp op = mUpdateOpPool.acquire();
        if (op == null) {
            op = new UpdateOp(cmd, positionStart, itemCount);
        } else {
            op.cmd = cmd;
            op.positionStart = positionStart;
            op.itemCount = itemCount;
        }
        return op;
    }

    void recycleUpdateOp(UpdateOp op) {
        mUpdateOpPool.release(op);
    }

    /**
     * Subclass for children of
     * {link RecyclerContainer}. Custom layout managers} are encouraged to create their own <code>LayoutParams</code> subclass
     * to store any additional required per-child view metadata about the layout.
     */
    public static class LayoutParams extends ComponentContainer.LayoutConfig {
        RecyclerContainer.ViewHolder mViewHolder, v2;
        int l = 0;
        boolean mInsetsDirty = true;
        final Rect mDecorInsets = new Rect();

        public LayoutParams(Context c, AttrSet attrs) {
            super(c, attrs);
        }

        public LayoutParams(int width, int height) {
            super(width, height);
        }

        public LayoutParams(ComponentContainer.LayoutConfig source) {
            super(source);
        }

        public LayoutParams(LayoutParams source) {
            super((RecyclerContainer.LayoutConfig) source);
        }

        public boolean viewNeedsUpdate() {
            return this.mViewHolder.needsUpdate();
        }

        public boolean isViewInvalid() {
            return this.mViewHolder.isInvalid();
        }

        public boolean isItemRemoved() {
            return this.mViewHolder.isRemoved();
        }

        public boolean isItemChanged() {
            return this.mViewHolder.isUpdated();
        }

        public int getViewPosition() {
            return this.mViewHolder.getPosition();
        }

        public int getViewLayoutPosition() {
            return this.mViewHolder.getLayoutPosition();
        }
    }

    /**
     * Observer base class for watching changes to an {link Adapter}.
     * See {link Adapter#registerAdapterDataObserver(AdapterDataObserver)}.
     */
    public static abstract class AdapterDataObserver {
        public void onChanged() {
            // Do nothing
        }

        public void onItemRangeChanged(int positionStart, int itemCount) {
            // do nothing
        }

        public void onItemRangeInserted(int positionStart, int itemCount) {
            // do nothing
        }

        public void onItemRangeRemoved(int positionStart, int itemCount) {
            // do nothing
        }
    }

    static class AdapterDataObservable extends Observable<AdapterDataObserver> {
        public boolean hasObservers() {
            return !mObservers.isEmpty();
        }

        public void notifyChanged() {
            // since onChanged() is implemented by the app, it could do anything, including
            // removing itself from {link mObservers} - and that could cause problems if
            // an iterator is used on the ArrayList {link mObservers}.
            // to avoid such problems, just march thru the list in the reverse order.
            for (int i = mObservers.size() - 1; i >= 0; i--) {
                mObservers.get(i).onChanged();
            }
        }

        public void notifyItemRangeChanged(int positionStart, int itemCount) {
            // since onItemRangeChanged() is implemented by the app, it could do anything, including
            // removing itself from {link mObservers} - and that could cause problems if
            // an iterator is used on the ArrayList {link mObservers}.
            // to avoid such problems, just march thru the list in the reverse order.
            for (int i = mObservers.size() - 1; i >= 0; i--) {
                mObservers.get(i).onItemRangeChanged(positionStart, itemCount);
            }
        }

        public void notifyItemRangeInserted(int positionStart, int itemCount) {
            // since onItemRangeInserted() is implemented by the app, it could do anything,
            // including removing itself from {link mObservers} - and that could cause problems if
            // an iterator is used on the ArrayList {link mObservers}.
            // to avoid such problems, just march thru the list in the reverse order.
            for (int i = mObservers.size() - 1; i >= 0; i--) {
                mObservers.get(i).onItemRangeInserted(positionStart, itemCount);
            }
        }

        public void notifyItemRangeRemoved(int positionStart, int itemCount) {
            // since onItemRangeRemoved() is implemented by the app, it could do anything, including
            // removing itself from {link mObservers} - and that could cause problems if
            // an iterator is used on the ArrayList {link mObservers}.
            // to avoid such problems, just march thru the list in the reverse order.
            for (int i = mObservers.size() - 1; i >= 0; i--) {
                mObservers.get(i).onItemRangeRemoved(positionStart, itemCount);
            }
        }
    }
}